/* eslint-disable @typescript-eslint/no-use-before-define */
import React, { ReactElement } from 'react';
import {
  EmitterSubscription,
  DeviceEventEmitter,
  AppState
} from 'react-native';
import {
  pipe,
  filter,
  map,
  values,
  flatten,
  uniq,
  findIndex,
  remove,
  propEq,
  split,
  keys,
  update
} from 'ramda';
import {
  RtcChannel,
  FacePositionInfo,
  AudioVolumeInfo
} from 'react-native-agora';
import { getFreeDiskStorageSync } from 'react-native-device-info';

import {
  Dispatch,
  State,
  FinishProblemData,
  LiveUser,
  UserLeaveRoomData,
  UserJoinRoomData,
  InstantAnswerInfo,
  FinishInstantData,
  ClassRoomProps,
  PPT,
  StartPPTProps,
  InstantRes,
  RollCallInfo
} from './types';
import { ClassApi } from '../../api';
import navigator from '../../util/navigator';
import { RoundInfo, InstantAnswer } from '../../api/class/getRoundList';
import {
  RoundType,
  UserRole,
  ProblemRoundStatus,
  LessonStatus,
  ClassRoomType,
  ReportEvent,
  ReplayType,
  GetLocalVideoUrlType,
  PPTType,
  VideoType,
  PPTStatus,
  ShowToastType,
  PROBLEM_TYPE,
  AUDIO_SCENE_ID,
  AnswerState
} from './constant';
import {
  CLASS_FINISH_PROBLEM,
  CLASS_USER_LEAVE_ROOM,
  CLASS_LESSON_START,
  CLASS_USER_JOIN_ROOM,
  CLASS_FINISH_INSTANT,
  CLASS_LESSON_SIGN,
  CLASS_TEACHER_AUDIO,
  CLASS_SHOW_FACE
} from '../../util/pushMsg';
import { getUserInfo as getLocalUserInfo, getUserInfo } from '../../store/user';
import {
  joinChannel,
  setAudioAvailable,
  setVideoAvailable
} from '../../util/RTC/rtcEngine';

import { modalClose, modalOpen, toolTipOpen } from '../../util/global';
import { CurrentRoundInfo } from '../../api/class/getRoomInfo';
import Log from '../../common/log';
import customAwait from '../../util/customAwait';
import { isIOS, IS_SUPPORT_FACE_DETECTED } from '../../constant';
import PortraitAlert from '../../components/Alert/PortraitAlert';
import {
  classRoomHaveVodRecordWithLessonId,
  clearRoomInfo,
  getReplayDuration,
  getRoomInfo,
  updateCurrentRoundInfo,
  updateUserGoldCoinNum,
  updateClassRoomDuration
} from '../../store/classRoom';
import { Progress } from '../../components/MediaPlayer/index.d';
import tracker from '../../util/actionTracker';
import {
  showClassRoom,
  endWatchClassRoomReplay,
  startWatchClassRoomReplay,
  watchingClassRoomReplay,
  videoPlayerError,
  getVideoInClassRoomFailed,
  getVideoInClassRoomUnfinished
} from '../../constant/events';
import { shellExec, systemUtil } from '../../util/native';
import Alert from '../../components/Alert';
import {
  deleteOtherVideoTask,
  filterExcludeVideoTasks,
  getVideoTaskIsFinished,
  pauseDownloadByParentId,
  startDownload,
  startDownloadByParentId,
  startInitDownloadVideoTasks,
  getCurrentDownloadVideoTask,
  setUnStartVideoTaskPaused,
  setUnStartVideoTaskDownload,
  continueDownload,
  pauseDownloadVideoTask
} from '../../util/videoDownload/videoDownloadManage';
import {
  DownloadVideoTaskProps,
  InitVideoTaskProps
} from '../../util/videoDownload/types';
import {
  DOWNLOAD_PATH,
  DOWNLOAD_VIDEO_TASK_FAIL,
  VIDEO_TYPE
} from '../../util/videoDownload/constant';
import {
  getVideoTasksByParentId,
  getAllStoreVideoTasks,
  getDownloadingStoreVideoTasks
} from '../../util/videoDownload/videoDownloadStore';
import { getScreenInfo, ScreenOrientation } from '../../util/screen';
import {
  ProblemAnswer,
  ProblemResult,
  SmallProblemAnswer,
  SmallProblemDetail
} from '../../api/common/problem';
import { getCurrentTime, setCurrentTime } from './store/currentTime';
import { setClassRoomCurrentTime } from './store/classRoomCurrentTime';
import { setSignInTime } from './store/sign';
import {
  cancelStopProgressTimer,
  clearCoinProgressTimer,
  setCoinProgress,
  startCountCoinProgress,
  stopCoinProgressTimer
} from './store/coinProgress';
import {
  emitFacePositionChange,
  emitShowFaceSuccess,
  emitSignInSuccess,
  emitSubmitSuccess
} from './event';
import {
  LessonSignData,
  ShowFaceData,
  TeacherAudio,
  TeacherAudioData
} from '../../util/pushMsg/types';
import { REACT_NATIVE_FACE_CHANGE } from '../../constant/nativeEvent';
import { UploadUserInstantAnswerRes } from '../../api/class/uploadUserInstantAnswer';
import { spliceAudio } from './util/spliceAudio';

const TAG = 'ClassRoom';
const DOWNLOAD_TAG = 'downloadResource';
const ALL_SUBMIT_ANSWER_CLOSE_PROBLEM_TIME = 2000;
const SYNC_ROUND_INFO_BUFFER = 5000;
const MAX_DISAPPEAR_FACE_TIME = 3 * 60 * 1000;
const REPLAY_REPORT_TIME = 10 * 1000;
const REPLAY_COUNT_TIME = 1000;
const MIN_PREPARE_ROLL_CALL_TIME = 5000;

let dispatch: Dispatch;
let state: State;

let localLessonId = 0;
let problemRemainTime = 0; // 答题倒计时
let replayDuration = 0; // 回放最大时间
let haveTeacherAudio = false; // 是否有老师音频

let finishProblemSubscription: EmitterSubscription;
let userJoinRoomSubscription: EmitterSubscription;
let userLeaveRoomSubscription: EmitterSubscription;
let lessonStartSubscription: EmitterSubscription;
let finishInstantSubscription: EmitterSubscription;
let lessonSignSubscription: EmitterSubscription;
let showFaceSubscription: EmitterSubscription;
let teacherAudioSubscription: EmitterSubscription;
let faceChangeSubscription: EmitterSubscription;
let failSubscription: EmitterSubscription;

let beforeLessonStartTimer: NodeJS.Timer | null;
let checkTimer: NodeJS.Timer;
let faceTimer: NodeJS.Timer | null;
let replayTimer: NodeJS.Timer | null;
let replayReportTimer: NodeJS.Timer | null;
let allFinishProblemTimer: NodeJS.Timer | null;
let checkDownloadFailTimer: NodeJS.Timer | null;
let barStatusChangeTimer: NodeJS.Timer | null;
let microphoneTimer: NodeJS.Timer | null;
let reload = (): void => {};

let broadcasterRtcChannel: RtcChannel | null;

let pptPlayer: { [key: string]: any } = {};
let pptContainer: any;

let nextSeekNumber = -1;
let haveSeek = false; // 是否有进行中的seek

let allVideoList: string[] = []; // 全部视频列表，用于调整视频的下载顺序
const initRollCallUserInfo = { userName: '', userId: -1 };

const { width } = getScreenInfo({
  screenOrientation: ScreenOrientation.Landscape
});

export default (d: Dispatch, s: State): void => {
  dispatch = d;
  state = s;
};

export const setLocalLessonId = (lessonId: number): void => {
  localLessonId = lessonId;
};

export const initClassRoomData = (value: ClassRoomProps): void => {
  Log.i(TAG, JSON.stringify({ event: 'initClassRoomData', value }));
  dispatch({ type: 'INIT_CLASS_ROOM', value });
};

export const setUsers = (value: LiveUser[]): void => {
  Log.i(TAG, JSON.stringify({ event: 'setUsers', value }));
  dispatch({ type: 'SET_USERS', value });
};

export const setTalkingUsers = (value: LiveUser[]): void => {
  dispatch({ type: 'SET_TALKING_USERS', value });
};

export const setShowLiveEnd = (value: boolean): void => {
  Log.i(TAG, JSON.stringify({ event: 'setShowLiveEnd', value }));
  dispatch({ type: 'SET_SHOW_LIVE_END', value });
};

export const setShowProblemResult = (value: boolean): void => {
  Log.i(TAG, JSON.stringify({ event: 'setShowProblemResult', value }));
  dispatch({ type: 'SET_SHOW_PROBLEM_RESULT', value });
};

export const setShowDownloadResource = (value: boolean): void => {
  Log.i(TAG, JSON.stringify({ event: 'setShowDownloadResource', value }));
  dispatch({ type: 'SET_SHOW_DOWNLOAD_RESOURCE', value });
};

export const setShowProblemWait = (value: boolean): void => {
  Log.i(TAG, JSON.stringify({ event: 'setShowProblemWait', value }));
  dispatch({ type: 'SET_SHOW_PROBLEM_WAIT', value });
};

export const setShowPrepare = (value: boolean): void => {
  Log.i(TAG, JSON.stringify({ event: 'setShowPrepare', value }));
  dispatch({ type: 'SET_SHOW_PREPARE', value });
};

export const setRounds = (value: RoundInfo[]): void => {
  dispatch({ type: 'SET_ROUNDS', value });
};

export const setTeacherName = (value: string): void => {
  dispatch({ type: 'SET_TEACHER_NAME', value });
};

export const updateRounds = (value: RoundInfo[]): void => {
  dispatch({ type: 'UPDATE_ROUNDS', value });
};

export const setIsJoinedChannel = (value: boolean): void => {
  Log.i(TAG, JSON.stringify({ event: 'setIsJoinedChannel', value }));
  dispatch({ type: 'SET_IS_JOINED_CHANNEL', value });
};

export const setTotalTime = (value: number): void => {
  Log.i(TAG, JSON.stringify({ event: 'setTotalTime', value }));
  dispatch({ type: 'SET_TOTAL_TIME', value });
};

export const setProblemResult = (value: ProblemResult): void => {
  Log.i(TAG, JSON.stringify({ event: 'setProblemResult', value }));
  dispatch({ type: 'SET_PROBLEM_RESULT', value });
};

export const setMustDownloadVideoUrls = (value: string[]): void => {
  Log.i(TAG, JSON.stringify({ event: 'setMustDownloadVideoUrls', value }));
  dispatch({ type: 'SET_MUST_DOWNLOAD_VIDEO_URLS', value });
};

export const setNeedDownloadVideoNum = (value: number): void => {
  Log.i(TAG, JSON.stringify({ event: 'setNeedDownloadVideoNum', value }));
  dispatch({ type: 'SET_NEED_DOWNLOAD_VIDEO_NUM', value });
};

export const setShowOpenMicrophone = (value: boolean): void => {
  Log.i(TAG, JSON.stringify({ event: 'setShowOpenMicrophone', value }));
  dispatch({ type: 'SET_SHOW_OPEN_MICROPHONE', value });
};

export const setPPTList = (value: PPT[]): void => {
  dispatch({ type: 'SET_PPT_LIST', value });
};

export const setSoundUrl = (value?: TeacherAudio): void => {
  Log.i(TAG, JSON.stringify({ event: 'setSoundUrl', value }));
  dispatch({ type: 'SET_SOUND_URL', value });
};

export const setSoundUrlList = (value: TeacherAudio[]): void => {
  Log.i(TAG, JSON.stringify({ event: 'setSoundUrlList', value }));
  dispatch({ type: 'SET_SOUND_URL_LIST', value });
};

export const setCurrentPPT = (value: PPT): void => {
  Log.i(TAG, JSON.stringify({ event: 'setCurrentPPT', value }));
  dispatch({ type: 'SET_CURRENT_PPT', value });
};

export const setIsOpenMicrophone = (value: boolean): void => {
  const { isOpenMicrophone } = state;
  if (!isOpenMicrophone && value) {
    showOpenMicrophone();
  }

  if (isOpenMicrophone && !value) {
    showOpenMicrophone();
  }

  dispatch({ type: 'SET_IS_OPEN_MICROPHONE', value });
};

export const setInstantAnswerInfo = (value: InstantAnswerInfo): void => {
  Log.i(TAG, JSON.stringify({ event: 'setInstantAnswerInfo', value }));
  dispatch({ type: 'SET_INSTANT_ANSWER_INFO', value });
};

export const hideInstantAnswer = (): void => {
  const { instantAnswerInfo } = state;
  setInstantAnswerInfo({ ...instantAnswerInfo, show: false });
};

export const setClassRoomChangeTime = (value: number): void => {
  Log.i(TAG, JSON.stringify({ event: 'setClassRoomChangeTime', value }));
  dispatch({ type: 'SET_CLASSROOM_CHANGE_TIME', value });
};

export const setShowLoading = (value: boolean): void => {
  Log.i(TAG, JSON.stringify({ event: 'setShowLoading', value }));
  dispatch({ type: 'SET_SHOW_LOADING', value });
};

export const setShowContainerLoading = (value: boolean): void => {
  dispatch({ type: 'SET_SHOW_CONTAINER_LOADING', value });
};

export const setShowBar = (value: boolean): void => {
  Log.i(TAG, JSON.stringify({ event: 'setShowBar', value }));
  dispatch({ type: 'SET_SHOW_BAR', value });
};

export const setShowToastType = (value: ShowToastType): void => {
  Log.i(TAG, JSON.stringify({ event: 'setShowToastType', value }));
  dispatch({ type: 'SET_SHOW_TOAST_TYPE', value });
};

export const setShowSignIn = (value: boolean): void => {
  Log.i(TAG, JSON.stringify({ event: 'setShowSignIn', value }));
  dispatch({ type: 'SET_SHOW_SIGN_IN', value });
};

export const setNeedSign = (value: boolean): void => {
  Log.i(TAG, JSON.stringify({ event: 'setNeedSign', value }));
  dispatch({ type: 'SET_NEED_SIGN', value });
};

export const setShowSignSuccess = (value: boolean): void => {
  Log.i(TAG, JSON.stringify({ event: 'setShowSignSuccess', value }));
  dispatch({ type: 'SET_SHOW_SIGN_SUCCESS', value });
};

export const setIsMute = (value: boolean): void => {
  Log.i(TAG, JSON.stringify({ event: 'setIsMute', value }));
  dispatch({ type: 'SET_IS_MUTE', value });
};

export const setShowLock = (value: boolean): void => {
  Log.i(TAG, JSON.stringify({ event: 'setShowLock', value }));
  mutePPT(value);
  dispatch({ type: 'SET_SHOW_LOCK', value });
};

export const setParentStudentId = (value: string): void => {
  Log.i(TAG, JSON.stringify({ event: 'setParentStudentId', value }));
  dispatch({ type: 'SET_PARENT_STUDENT_ID', value });
};

export const fetchFailed = (value = true): void => {
  Log.i(TAG, JSON.stringify({ event: 'fetchFailed', value }));
  dispatch({ type: 'FETCH_FAILED', value });
};

// 获取指定用户展示信息
export const getLiveUserInfo = (userInfo: UserJoinRoomData): void => {
  const {
    userRole,
    goldCoinNum,
    userNickName,
    isSubmit,
    userId,
    headUrl,
    problemId
  } = userInfo;

  if (userRole === UserRole.Unknown) {
    return;
  }

  let { users } = state;
  const { currentPPT } = state;
  const { problem } = currentPPT;
  const isExistUserIndex = users.findIndex(
    (user): boolean => Number(user.userId) === Number(userId)
  );

  let haveAnswer = false;
  if (problem) {
    const { problemId: currentProblemId } = problem;
    if (currentProblemId === problemId) {
      haveAnswer = !!isSubmit;
    }
  }

  if (isExistUserIndex > -1) {
    users = users.map(
      (user): LiveUser => {
        if (Number(userId) === Number(user.userId)) {
          const resGoldCoinNum =
            goldCoinNum > user.goldCoinNum ? goldCoinNum : user.goldCoinNum;
          return {
            ...user,
            userRole,
            goldCoinNum: resGoldCoinNum,
            userNickName,
            haveAnswer
          };
        }
        return user;
      }
    );
  } else {
    users = [
      ...users,
      {
        userId,
        userRole,
        goldCoinNum,
        userNickName,
        haveAnswer,
        headUrl
      }
    ];
  }

  setUsers(users);
};

export const leaveAllChannel = async (): Promise<boolean> => {
  if (!broadcasterRtcChannel) return true;

  await broadcasterRtcChannel.leaveChannel();
  broadcasterRtcChannel.removeAllListeners();
  broadcasterRtcChannel.destroy();
  broadcasterRtcChannel = null;
  return true;
};

export const clearFaceTimer = (): void => {
  if (faceTimer) {
    clearInterval(faceTimer);
    faceTimer = null;
  }
};

export const updateFaces = (faceNumber: number): void => {
  if (!IS_SUPPORT_FACE_DETECTED) return;

  emitFacePositionChange({ faceNumber });

  if (faceNumber === 0) {
    stopCoinProgressTimer();
  } else {
    cancelStopProgressTimer();
  }

  const { type } = state;
  if (type === ClassRoomType.Vod) return;

  if (faceNumber === 0) {
    if (!faceTimer) {
      faceTimer = setTimeout((): void => {
        reportEvent(ReportEvent.OutOfCameraOver3Minutes);
      }, MAX_DISAPPEAR_FACE_TIME);
    }
  } else {
    clearFaceTimer();
  }
};

export const clearMicrophoneTimer = (): void => {
  if (microphoneTimer) {
    clearTimeout(microphoneTimer);
    microphoneTimer = null;
  }
};

export const userCanNotOpenMicrophone = (): void => {
  const needOpenMicrophone = getNeedOpenMicrophone();
  if (microphoneTimer) return;

  if (!needOpenMicrophone) {
    microphoneTimer = setTimeout((): void => {
      clearMicrophoneTimer();
    }, 3000);
    setAudioAvailable(false, 'userCanNotOpenMicrophone');
  }
};

export const audioVolumeIndication = (
  speakers: AudioVolumeInfo[],
  totalVolume: number
): void => {
  const { users } = state;
  let { currentTalkingUsers } = state;
  const { userId } = getUserInfo();

  const isLocalUser = speakers.find((speaker): boolean => speaker.uid === 0);
  if (isLocalUser) {
    userCanNotOpenMicrophone();
    const { volume, vad } = isLocalUser;
    if (volume > 0 || vad === 1) {
      let currentUser = currentTalkingUsers.find(
        (user): boolean => Number(user.userId) === Number(userId)
      );
      if (!currentUser) {
        currentUser = users.find((user): boolean => user.userId === userId);
        if (currentUser) {
          currentTalkingUsers = [...currentTalkingUsers, currentUser];
        }
      }
    } else {
      currentTalkingUsers = currentTalkingUsers.filter(
        (user): boolean => user.userId !== userId
      );
    }
    setTalkingUsers(currentTalkingUsers);
    return;
  }

  currentTalkingUsers = currentTalkingUsers.filter(
    (user): boolean => user.userId === userId
  );

  const speakerUsers = users.filter(
    (user): boolean =>
      !!speakers.find((u): boolean => Number(u.uid) === Number(user.userId))
  );
  currentTalkingUsers = [...currentTalkingUsers, ...speakerUsers];

  setTalkingUsers(currentTalkingUsers);
};

export const getAgoraToken = async (
  channelName: string,
  type: ClassRoomType
): Promise<boolean> => {
  if (type === ClassRoomType.Vod) {
    const [joinError, channel] = await customAwait(
      joinChannel({
        channel: channelName,
        token: '',
        onFacePositionChange: (
          imageWidth: number,
          imageHeight: number,
          faces: FacePositionInfo[]
        ): void => {
          if (!isIOS) return;
          updateFaces(faces.length);
        }
      })
    );
    if (!channel || !!joinError) {
      Log.e(
        TAG,
        JSON.stringify({
          event: 'getAgoraToken joinChannel error',
          joinError
        })
      );
      return false;
    }
    if (channel) {
      broadcasterRtcChannel = channel;
      setIsJoinedChannel(true);
      return true;
    }
    return false;
  }

  const [error, res] = await customAwait(
    ClassApi.getAgoraToken({ channelName })
  );
  if (!res || !!error || res.code !== 0) {
    return false;
  }
  const { token } = res.data;

  Log.i(
    TAG,
    JSON.stringify({
      event: 'getAgoraToken joinChannel',
      channelName
    })
  );

  const [joinError, channel] = await customAwait(
    joinChannel({
      channel: channelName,
      token,
      getToken: (): Promise<string> => {
        return new Promise((resolve, reject): void => {
          ClassApi.getAgoraToken({ channelName })
            .then((getTokenRes): void => {
              if (getTokenRes.code === 0) {
                const { token: newToken } = getTokenRes.data;
                resolve(newToken);
              } else {
                reject();
              }
            })
            .catch((): void => {
              reject();
            });
        });
      },
      onFacePositionChange: (
        imageWidth: number,
        imageHeight: number,
        faces: FacePositionInfo[]
      ): void => {
        if (!isIOS) return;
        updateFaces(faces.length);
      },
      audioVolumeIndication
    })
  );
  if (!channel || !!joinError) {
    Log.e(
      TAG,
      JSON.stringify({
        event: 'getAgoraToken joinChannel error',
        joinError
      })
    );
    return false;
  }
  if (channel) {
    broadcasterRtcChannel = channel;
    setIsJoinedChannel(true);
    return true;
  }
  return false;
};

export const lessonFinished = (): void => {
  const { type } = state;
  if (type === ClassRoomType.Vod) return;

  ClassApi.lessonFinished({ lessonId: localLessonId });
};

export const leaveRoom = (): void => {
  const { type } = state;
  if (type === ClassRoomType.Vod) return;

  ClassApi.leaveRoom({ lessonId: localLessonId });
};

export const back = (): void => {
  const { showProblemWait, showLiveEnd, type, lesson } = state;
  const { lessonResourceId } = lesson;

  if (showProblemWait) return;

  leaveAllChannel().then((): void => {
    removeListener();
    clearAllTimer();
    modalClose();
    changeBarStatus(false);
    setCurrentPPT({
      type: PPTType.Unknown,
      index: -1,
      roundId: -1,
      status: PPTStatus.Unknown,
      lessonId: -1
    });
    hideInstantAnswer();
    setShowPrepare(false);
    setShowLiveEnd(false);
    setShowLock(false);

    if (showLiveEnd) {
      clearRoomInfo(localLessonId);
    } else {
      leaveRoom();
    }

    if (type === ClassRoomType.Vod) {
      pauseDownloadByParentId(state.lesson.lessonResourceId);
      tracker.reportAction(endWatchClassRoomReplay, {
        objectId: lessonResourceId
      });
    }
    navigator.goBack();
  });
};

export const startClassRoomWithProblemRound = (
  round: CurrentRoundInfo
): void => {
  const {
    doProblemProgress,
    explainVideoProgress,
    roundStatus,
    roundId,
    problemAnswer,
    problemResult,
    submitProblemUsers
  } = round;
  const { rounds, PPTList } = state;

  let currentRound = rounds.find((r): boolean => r.roundId === roundId);

  if (!currentRound) return;

  const { roundInfoProblem } = currentRound;
  const { answerDuration } = roundInfoProblem;
  const { userId: localUserId } = getUserInfo();

  const isAfterDiscuss =
    roundStatus === ProblemRoundStatus.DoProblemAfterDiscuss;

  const haveSubmitAnswer =
    submitProblemUsers.findIndex(
      (userId): boolean => String(userId) === String(localUserId)
    ) > -1;

  currentRound = {
    ...currentRound,
    roundInfoProblem: {
      ...roundInfoProblem,
      explainVideoProgress,
      answerDurationAfterDiscuss: answerDuration - doProblemProgress,
      doProblemProgress,
      haveSubmitAnswer,
      userAnswer: problemAnswer,
      answerResult: problemResult
    }
  };

  if (isAfterDiscuss) {
    const currentPPT = PPTList.find(
      (ppt): boolean => ppt.roundId === roundId && ppt.type === PPTType.Problem
    );
    if (currentPPT) {
      startPPT({
        ppt: {
          ...currentPPT,
          round: currentRound
        },
        currentRoundInfo: round
      });
    }
  } else if (roundStatus === ProblemRoundStatus.Analysis) {
    const currentPPT = PPTList.find(
      (ppt): boolean => ppt.roundId === roundId && ppt.type === PPTType.Video
    );
    if (currentPPT) {
      startPPT({
        ppt: {
          ...currentPPT,
          round: currentRound
        },
        currentRoundInfo: round
      });
    }
  }
};

export const startClassRoomWithVideoRound = (round: CurrentRoundInfo): void => {
  const { rounds, PPTList } = state;
  const { roundProgress, roundId } = round;

  let currentRound = rounds.find((r): boolean => r.roundId === roundId);

  if (!currentRound) return;

  currentRound = {
    ...currentRound,
    roundInfoVideo: {
      ...currentRound.roundInfoVideo,
      roundProgress
    }
  };

  const isMainRound = !!rounds.find((r): boolean => r.roundId === roundId);
  if (!isMainRound) return;

  const currentPPT = PPTList.find((ppt): boolean => ppt.roundId === roundId);
  if (!currentPPT) return;

  startPPT({
    ppt: {
      ...currentPPT,
      round: currentRound
    },
    currentRoundInfo: round
  });
};

export const startClassRoomWithRollCallRound = (
  round: CurrentRoundInfo
): void => {
  const { rounds, PPTList } = state;
  const {
    doProblemProgress,
    explainVideoProgress,
    roundId,
    roundStatus,
    answerState
  } = round;

  let currentRound = rounds.find((r): boolean => r.roundId === roundId);
  if (!currentRound) return;

  currentRound = {
    ...currentRound,
    roundInfoRollCall: {
      ...currentRound.roundInfoRollCall,
      doProblemProgress,
      explainVideoProgress
    }
  };

  const isMainRound = !!rounds.find((r): boolean => r.roundId === roundId);
  if (!isMainRound) return;

  let videoType: VideoType = VideoType.Unknown;
  if (roundStatus === ProblemRoundStatus.DoProblemAfterDiscuss) {
    videoType = VideoType.RollCall;
  } else if (answerState === AnswerState.Right) {
    videoType = VideoType.SubmitRight;
  } else {
    videoType = VideoType.SubmitWrong;
  }

  const currentPPT = PPTList.find(
    (ppt): boolean => ppt.roundId === roundId && ppt.videoType === videoType
  );
  if (!currentPPT) return;

  startPPT({
    ppt: {
      ...currentPPT,
      round: currentRound
    },
    currentRoundInfo: round
  });
};

export const syncUsers = (remoteUsers: LiveUser[]): void => {
  const { users } = state;

  let currentUsers = filter(
    (item: LiveUser): boolean =>
      remoteUsers.findIndex(
        ({ userId: localUserId }): boolean =>
          Number(localUserId) === Number(item.userId)
      ) !== -1
  )(users);

  currentUsers = currentUsers.map(
    (user): LiveUser => {
      const remoteUser = remoteUsers.find(
        (u): boolean => Number(u.userId) === Number(user.userId)
      );
      if (remoteUser) {
        return {
          ...user,
          ...remoteUser
        };
      }
      return user;
    }
  );

  const newUsers = filter(
    (item: LiveUser): boolean =>
      users.findIndex(
        ({ userId: localUserId }): boolean =>
          Number(localUserId) === Number(item.userId)
      ) === -1
  )(remoteUsers);

  const resUsers = [...currentUsers, ...newUsers];

  setUsers(resUsers);
};

export const checkClassRoomInfo = async (): Promise<void> => {
  const res = await ClassApi.getRoomInfo({ lessonId: localLessonId });
  const { code, data } = res;
  if (code !== 0) return;

  const { currentRoundInfo, lessonStatus } = data;
  let { users } = data;
  const { rounds, currentPPT } = state;
  const { round: localCurrentRound } = currentPPT;

  if (!localCurrentRound) return;

  if (lessonStatus === LessonStatus.End) {
    Log.i(
      TAG,
      JSON.stringify({
        event: 'checkClassRoomInfo end'
      })
    );
    lessonEnd();
    return;
  }

  if (currentRoundInfo) {
    const { submitProblemUsers = [] } = currentRoundInfo;

    users = users.map(
      (user): LiveUser => {
        return {
          ...user,
          haveAnswer: !!submitProblemUsers.find(
            (u): boolean => Number(u) === Number(user.userId)
          )
        };
      }
    );
  }

  syncUsers(users);

  const { roundType } = currentRoundInfo;

  const localCurrentRoundIndex = rounds.findIndex(
    (round): boolean => round.roundId === localCurrentRound.roundId
  );
  const currentRoundInfoIndex = rounds.findIndex(
    (round): boolean => round.roundId === currentRoundInfo.roundId
  );

  // 服务器环节比端上环节进度慢
  if (
    currentRoundInfoIndex !== -1 &&
    localCurrentRoundIndex !== -1 &&
    currentRoundInfoIndex < localCurrentRoundIndex
  ) {
    Log.e(
      TAG,
      JSON.stringify({
        event: 'checkClassRoomInfo progress error',
        currentRoundInfo,
        localCurrentRound
      })
    );
    return;
  }

  if (localCurrentRound.roundId !== currentRoundInfo.roundId) {
    switch (roundType) {
      case RoundType.Problem:
        startClassRoomWithProblemRound(currentRoundInfo);
        break;
      case RoundType.Video:
        startClassRoomWithVideoRound(currentRoundInfo);
        break;
      case RoundType.RollCall:
        startClassRoomWithRollCallRound(currentRoundInfo);
        break;
      default:
        break;
    }
    return;
  }

  // 如果当前是视频环节或者做题环节的视频解析阶段，到了视频后几秒，则不做同步
  if (currentPPT.type === PPTType.Video) {
    const { totalTime } = state;
    const currentTime = getCurrentTime();
    if (totalTime > 0 && totalTime - currentTime < 2000) {
      Log.e(
        TAG,
        JSON.stringify({
          event: 'checkClassRoomInfo main video will end',
          currentPPT,
          currentTime,
          totalTime
        })
      );
      return;
    }
  }

  syncPPT(currentRoundInfo);
};

export const clearCheckTimer = (): void => {
  if (checkTimer) {
    clearInterval(checkTimer);
  }
};

export const startCheckClassRoom = (): void => {
  const { type } = state;
  if (type === ClassRoomType.Vod) return;

  clearCheckTimer();
  // 轮询房间信息
  checkTimer = setInterval((): void => {
    checkClassRoomInfo();
  }, 15 * 1000);
};

export const initRoundInfo = (round: RoundInfo): void => {
  const { roundType } = round;

  let roundStatus = ProblemRoundStatus.Analysis;
  if (roundType === RoundType.Problem || roundType === RoundType.RollCall) {
    roundStatus = ProblemRoundStatus.DoProblemAfterDiscuss;
  }

  const currentRound = {
    roundId: round.roundId,
    roundType,
    roundProgress: 0,
    doProblemProgress: 0,
    explainVideoProgress: 0,
    roundStatus,
    submitProblemUsers: [],
    problemAnswer: {
      problemId: 0,
      withSmall: false,
      answer: [],
      smallAnswers: []
    },
    problemResult: {
      problemId: 0,
      withSmall: false,
      isCorrect: false,
      results: [],
      addGoldCoinNum: 0
    }
  };

  switch (roundType) {
    case RoundType.Problem:
      startClassRoomWithProblemRound(currentRound);
      break;
    case RoundType.Video:
      startClassRoomWithVideoRound(currentRound);
      break;
    case RoundType.RollCall:
      startClassRoomWithRollCallRound(currentRound);
      break;
    default:
      break;
  }
};

export const lessonStart = (): void => {
  const { rounds, showPrepare } = state;
  if (!showPrepare) return;

  setShowPrepare(false);
  startCheckClassRoom();

  initRoundInfo(rounds[0]);
  Log.i(
    TAG,
    JSON.stringify({
      event: 'lessonStart'
    })
  );
};

export const clearBeforeLessonStartTimer = (): void => {
  if (beforeLessonStartTimer) {
    clearTimeout(beforeLessonStartTimer);
    beforeLessonStartTimer = null;
  }
};

export const stopReceiveLessonStartMsg = (): void => {
  if (lessonStartSubscription) {
    lessonStartSubscription.remove();
  }
};

export const earlyEntry = (intervalBeforeLessonStart: number): void => {
  setShowPrepare(true);

  Log.i(
    TAG,
    JSON.stringify({
      event: 'earlyEntry',
      intervalBeforeLessonStart
    })
  );

  if (intervalBeforeLessonStart > 0) {
    clearBeforeLessonStartTimer();
    beforeLessonStartTimer = setTimeout((): void => {
      stopReceiveLessonStartMsg();
      lessonStart();
    }, intervalBeforeLessonStart);
  }
};

export const startReplayTimer = (): void => {
  const { lesson } = state;
  const { lessonResourceId } = lesson;
  if (!replayReportTimer) {
    replayReportTimer = setInterval((): void => {
      tracker.reportAction(watchingClassRoomReplay, {
        objectId: lessonResourceId,
        duration: replayDuration
      });
    }, REPLAY_REPORT_TIME);
  }
  if (!replayTimer) {
    replayTimer = setInterval((): void => {
      const { type } = state;
      replayDuration += 1000;
      updateClassRoomDuration(localLessonId, type)(replayDuration);
    }, REPLAY_COUNT_TIME);
  }
};

export const clearReplayTimer = (): void => {
  if (replayReportTimer) {
    clearInterval(replayReportTimer);
    replayReportTimer = null;
  }
  if (replayTimer) {
    clearInterval(replayTimer);
    replayTimer = null;
  }
};

export const entryClassRoom = async (): Promise<any> => {
  const { type, rounds, needSign } = state;
  let error;
  let res;
  if (type === ClassRoomType.Vod) {
    res = await getRoomInfo(localLessonId)(rounds[0]);
  } else if (type === ClassRoomType.Live) {
    [error, res] = await customAwait(
      ClassApi.getRoomInfo({
        lessonId: localLessonId
      })
    );
    if (error) {
      fetchFailed();
      reload = (): void => {
        entryClassRoom();
      };
      return;
    }
  }

  Log.i(
    TAG,
    JSON.stringify({
      event: 'entryClassRoom',
      res
    })
  );

  if (!res || res.code !== 0) {
    fetchFailed();
    reload = (): void => {
      entryClassRoom();
    };
    return;
  }

  const { data } = res;

  const {
    lessonStatus,
    currentRoundInfo,
    intervalBeforeLessonStart = 0
  } = data;

  if (lessonStatus === LessonStatus.UnStart) {
    Log.i(
      TAG,
      JSON.stringify({
        event: 'entryClassRoom unStart'
      })
    );
    return;
  }

  if (lessonStatus === LessonStatus.End) {
    Log.i(
      TAG,
      JSON.stringify({
        event: 'entryClassRoom end'
      })
    );
    lessonEnd();
    setShowDownloadResource(false);
    return;
  }

  let { users } = data;

  if (currentRoundInfo) {
    const { submitProblemUsers = [] } = currentRoundInfo;

    users = users.map(
      (user): LiveUser => {
        return {
          ...user,
          haveAnswer: !!submitProblemUsers.find(
            (u): boolean => Number(u) === Number(user.userId)
          )
        };
      }
    );
  }

  setUsers(users);

  if (lessonStatus === LessonStatus.EarlyEntry) {
    earlyEntry(intervalBeforeLessonStart);
    setShowDownloadResource(false);
    if (needSign) {
      setShowSignIn(true);
    } else {
      startCountCoinProgress();
    }
    return;
  }

  const { roundType } = currentRoundInfo;

  startCheckClassRoom();

  // 回放打点
  if (type === ClassRoomType.Vod) {
    replayDuration = getReplayDuration(localLessonId);
    startReplayTimer();
  }

  switch (roundType) {
    case RoundType.Problem:
      startClassRoomWithProblemRound(currentRoundInfo);
      break;
    case RoundType.Video:
      startClassRoomWithVideoRound(currentRoundInfo);
      break;
    case RoundType.RollCall:
      startClassRoomWithRollCallRound(currentRoundInfo);
      break;
    default:
      break;
  }
};

export const clearUserAnswerInfo = (): void => {
  let { users } = state;
  users = users.map(
    (user): LiveUser => ({
      ...user,
      haveAnswer: false
    })
  );
  setUsers(users);
};

export const uploadUserProblemRoundProgress = (
  round: RoundInfo,
  roundStatus: ProblemRoundStatus
): void => {
  Log.i(
    TAG,
    JSON.stringify({
      event: 'uploadUserProblemRoundProgress',
      round,
      roundStatus
    })
  );
  const { roundId } = round;
  const params = { roundId, lessonId: localLessonId, roundStatus };

  const { type } = state;
  if (type === ClassRoomType.Vod) return;

  ClassApi.userProblemRoundProgress(params);
};

// 提交空的互动题答案
export const submitEmptyAnswer = (
  roundId: number,
  answer: ProblemAnswer
): void => {
  const { type } = state;

  Log.i(
    TAG,
    JSON.stringify({
      event: 'submitEmptyAnswer',
      roundId,
      problemId: answer.problemId
    })
  );

  if (type === ClassRoomType.Vod) {
    ClassApi.submitReplayAnswer(
      {
        problemAnswer: answer,
        isAbort: true
      },
      false
    );
    return;
  }
  ClassApi.submitAnswer(
    {
      problemAnswer: answer,
      lessonId: localLessonId,
      roundId,
      useTime: 0,
      isAbort: true
    },
    false
  );
};

export const finishProblem = (isTimeout?: boolean): void => {
  Log.i(TAG, JSON.stringify({ event: 'finishProblem' }));

  const { currentPPT } = state;
  const { round: currentRound } = currentPPT;
  if (!currentRound) return;

  const { roundType, roundInfoProblem } = currentRound;

  if (roundType !== RoundType.Problem) {
    return;
  }

  const { roundId } = currentRound;

  const {
    commonExplainVideo,
    problemId,
    haveSubmitAnswer,
    problem
  } = roundInfoProblem;

  if (isTimeout && !haveSubmitAnswer && problem) {
    let smallProblemList: SmallProblemDetail[] = [];
    const { wordBlankfillingProblem, clozeProblem, readingProblem } = problem;

    if (wordBlankfillingProblem) {
      smallProblemList = wordBlankfillingProblem.smallProblems;
    } else if (clozeProblem) {
      smallProblemList = clozeProblem.smallProblems;
    } else if (readingProblem) {
      smallProblemList = readingProblem.smallProblems;
    }
    submitEmptyAnswer(roundId, {
      problemId,
      withSmall: smallProblemList.length > 0,
      answer: [],
      smallAnswers: smallProblemList.map(
        (p): SmallProblemAnswer => ({
          smallProblemId: p.smallProblemId,
          answer: []
        })
      )
    });
  }

  const { originVideoDownloadURL } = commonExplainVideo;

  setShowProblemWait(false);

  if (originVideoDownloadURL === '') {
    nextRound();
    return;
  }

  nextPPT();
  uploadUserProblemRoundProgress(currentRound, ProblemRoundStatus.Analysis);
};

const checkUserAnswer = (users: LiveUser[]): void => {
  const allSubmitAnswer =
    users.findIndex(
      (user): boolean => user.userRole === UserRole.Student && !user.haveAnswer
    ) === -1;

  Log.i(
    TAG,
    JSON.stringify({
      event: 'checkUserAnswer',
      allSubmitAnswer,
      haveTeacherAudio
    })
  );

  if (haveTeacherAudio) return;

  // 全部用户都答完题，延时结束答题
  if (allSubmitAnswer) {
    clearAllFinishProblemTimer();
    allFinishProblemTimer = setTimeout((): void => {
      finishProblem();
    }, ALL_SUBMIT_ANSWER_CLOSE_PROBLEM_TIME);
  }
};

// 提交互动题答案
export const submitAnswer = (
  roundId: number,
  answer: ProblemAnswer,
  problemType: PROBLEM_TYPE
): void => {
  if (!answer.withSmall) {
    const hasAnswer =
      answer.answer.length > 0 &&
      answer.answer.every((item): boolean => !!item[0]);
    if (!hasAnswer) {
      toolTipOpen({
        desc: problemType === PROBLEM_TYPE.Single ? '请选择答案' : '请填写答案'
      });
      return;
    }
  }
  if (answer.withSmall) {
    const hasAnswer =
      answer.smallAnswers.length > 0 &&
      answer.smallAnswers.every((item): boolean => !!item.answer[0]);
    if (!hasAnswer) {
      toolTipOpen({
        desc: '请填写答案'
      });
      return;
    }
  }
  const { type } = state;

  // 回放提交互动题答案
  if (type === ClassRoomType.Vod) {
    ClassApi.submitReplayAnswer({
      problemAnswer: answer,
      isAbort: false
    }).then((res): void => {
      if (res.code === 0) {
        const { problemResult, addGoldCoin } = res.data;

        let { currentPPT, users } = state;
        if (currentPPT.type === PPTType.Unknown) return;

        users = users.map(
          (user): LiveUser => {
            const { userId } = getLocalUserInfo();
            if (Number(user.userId) === Number(userId)) {
              updateUserGoldCoinNum(
                localLessonId,
                type
              )(user.goldCoinNum + addGoldCoin);
              setShowProblemResult(true);
              setProblemResult({
                ...problemResult,
                addGoldCoinNum: addGoldCoin
              });
              return {
                ...user,
                goldCoinNum: user.goldCoinNum + addGoldCoin,
                haveAnswer: true
              };
            }
            return user;
          }
        );

        setUsers(users);
        checkUserAnswer(users);

        if (currentPPT && currentPPT.round) {
          const { roundInfoProblem } = currentPPT.round;
          currentPPT = {
            ...currentPPT,
            round: {
              ...currentPPT.round,
              roundInfoProblem: {
                ...roundInfoProblem,
                haveSubmitAnswer: true,
                answerResult: problemResult,
                userAnswer: undefined
              }
            }
          };
          setCurrentPPT(currentPPT);
        }
      }
    });
    return;
  }
  ClassApi.submitAnswer({
    problemAnswer: answer,
    lessonId: localLessonId,
    roundId,
    useTime: problemRemainTime * 1000,
    isAbort: false
  }).then((res): void => {
    if (res.code === 0) {
      const { problemResult, goldCoinNum } = res.data;

      let { users, currentPPT } = state;
      if (currentPPT.type === PPTType.Unknown) return;

      const { userId } = getLocalUserInfo();
      users = users.map(
        (user): LiveUser => {
          if (Number(user.userId) === Number(userId)) {
            const addGoldCoinNum = goldCoinNum - user.goldCoinNum;
            setShowProblemResult(true);
            setProblemResult({ ...problemResult, addGoldCoinNum });
            return {
              ...user,
              goldCoinNum,
              haveAnswer: true
            };
          }
          return user;
        }
      );

      setUsers(users);
      checkUserAnswer(users);

      if (currentPPT && currentPPT.round) {
        const { roundInfoProblem } = currentPPT.round;
        currentPPT = {
          ...currentPPT,
          round: {
            ...currentPPT.round,
            roundInfoProblem: {
              ...roundInfoProblem,
              haveSubmitAnswer: true,
              answerResult: problemResult,
              userAnswer: undefined
            }
          }
        };
        setCurrentPPT(currentPPT);
      }
    }
  });
};

export const getVideoName = (url: string): string => {
  // http://video.yutouedu.com/ak/rp/d-r-9d5994fac850bced229416638546c91d/hd.m3u8 -> 9d5994fac850bced229416638546c91d
  try {
    return url.split('r-')[1].split('/')[0];
  } catch (e) {
    return '';
  }
};

export const downloadVideos = async (
  videoUrls: string[],
  firstDownloadUrl: string
): Promise<void> => {
  const { lesson } = state;
  const { lessonResourceId, lessonName, lessonSource } = lesson;

  const cacheInfoRes = await ClassApi.getRoundListCacheInfo({
    lessonResourceId
  });

  if (cacheInfoRes.code === 0) {
    const { videos } = cacheInfoRes.data;

    const needDownloadVideoTasks: InitVideoTaskProps[] = [];
    const allDownloadVideoTasks: InitVideoTaskProps[] = [];
    const getDownloadVideoTasks = (
      urls: string[],
      size: number
    ): InitVideoTaskProps => {
      // http://video.yutouedu.com/ak/cp/p-r-cf62659025b523c5bd3d231c8777d786-c/hd.m3u8 -> cf62659025b523c5bd3d231c8777d786
      const title = getVideoName(urls[0]);
      return {
        parentId: lessonResourceId,
        parentTitle: lessonName,
        id: `${lessonResourceId}-${title}`,
        title,
        type: VIDEO_TYPE.hd,
        urls,
        size,
        source: lessonSource
      };
    };

    let needSize = 0;

    videos.forEach((item): void => {
      const { originVideoDownloadURL, originVideoSize } = item;

      if (videoUrls.includes(originVideoDownloadURL)) {
        needDownloadVideoTasks.push(
          getDownloadVideoTasks([originVideoDownloadURL], originVideoSize)
        );
        needSize += originVideoSize;
      }
      allDownloadVideoTasks.push(
        getDownloadVideoTasks([originVideoDownloadURL], originVideoSize)
      );
    });

    const freeSpace = isIOS
      ? getFreeDiskStorageSync() // Bytes
      : Number(systemUtil.getMemoryInfo().availMemory) * 1024 * 1024; // MB -> KB -> Bytes

    if (freeSpace < needSize) {
      modalOpen({
        Type: (): ReactElement => (
          <Alert
            title="温馨提示"
            subTitle="手机存储空间不足，无法加载课件资源包。为保障流畅体验，请先清理缓存再开始上课。"
            sureDesc="确定"
            sureAction={(): void => {
              back();
              modalClose();
            }}
          />
        ),
        needBack: true
      });
      return;
    }

    // 先暂停其他的下载任务，优先开始当前下载任务
    const downloadingVideos = getDownloadingStoreVideoTasks();
    downloadingVideos.forEach(({ parentId }): void => {
      pauseDownloadByParentId(parentId);
      setTimeout((): void => {
        startDownloadByParentId(parentId);
      }, 2000);
    });

    filterExcludeVideoTasks(allDownloadVideoTasks);
    startInitDownloadVideoTasks(needDownloadVideoTasks, true);
    startDownloadByParentId(lessonResourceId);

    if (firstDownloadUrl) {
      const tasks = getAllStoreVideoTasks();
      const taskId = `${lessonResourceId}-${getVideoName(firstDownloadUrl)}`;

      const currentVideoSeq = allVideoList.findIndex((item): boolean =>
        taskId.includes(item)
      );

      for (let i = 0; i < tasks.length; i += 1) {
        if (tasks[i].parentId === lessonResourceId) {
          const seq = allVideoList.findIndex((item): boolean =>
            String(tasks[i].id).includes(item)
          );
          if (seq !== -1 && seq < currentVideoSeq) {
            setUnStartVideoTaskPaused(tasks[i].id);
          } else if (seq >= currentVideoSeq) {
            setUnStartVideoTaskDownload(tasks[i].id);
          }
        }
      }

      const currentDownloadVideoTask = getCurrentDownloadVideoTask();
      if (currentDownloadVideoTask && currentDownloadVideoTask.id !== taskId) {
        pauseDownloadVideoTask(); // 暂停当前的下载任务
        setUnStartVideoTaskDownload(taskId);
        setTimeout((): void => {
          continueDownload();
        }, 150);
      }
    }
  }
};

export const getRoundUrls = (round: RoundInfo): string[] => {
  let originVideoUrl: string[] = [];
  if (round.roundType === RoundType.Video) {
    originVideoUrl = [round.roundInfoVideo.video.originVideoDownloadURL];
  } else if (round.roundType === RoundType.RollCall) {
    const { rollCallVideo, rightVideo, wrongVideo } = round.roundInfoRollCall;
    originVideoUrl = [
      rollCallVideo.originVideoDownloadURL,
      rightVideo.originVideoDownloadURL,
      wrongVideo.originVideoDownloadURL
    ];
  } else if (round.roundType === RoundType.Problem) {
    originVideoUrl = [
      round.roundInfoProblem.commonExplainVideo.originVideoDownloadURL
    ];
  }
  return originVideoUrl;
};

export const downloadRoundVideo = (
  rounds: RoundInfo[],
  startRoundId?: number
): void => {
  let startRoundIdIndex = rounds.findIndex(
    (round): boolean => round.roundId === startRoundId
  );
  if (startRoundIdIndex < 0) {
    startRoundIdIndex = 0;
  }

  // 过滤需要下载视频的环节，不管是直播还是回放，所有视频都放在队列中，从当前round开始下载
  const notPlayRounds = rounds.filter((r): boolean => {
    if (r.roundType === RoundType.Video) {
      return true;
    }
    if (r.roundType === RoundType.RollCall) {
      return true;
    }
    if (r.roundType === RoundType.Problem) {
      const { commonExplainVideo } = r.roundInfoProblem;
      const { originVideoDownloadURL } = commonExplainVideo;
      if (originVideoDownloadURL !== '') {
        return true;
      }
    }
    return false;
  });

  let mustDownloadRound: RoundInfo | null = null;
  const startRound = notPlayRounds.find(
    (round): boolean => round.roundId === startRoundId
  );
  if (startRound) {
    mustDownloadRound = startRound;
  } else if (notPlayRounds.length > 0) {
    [mustDownloadRound] = notPlayRounds;
  }

  const getValues = (data: any[]): any => {
    return map((value: any): any => {
      if (typeof value === 'object') {
        const valueList = values(value);
        return getValues(valueList);
      }
      return value;
    }, data);
  };

  const videoList = pipe(
    getValues,
    flatten,
    filter((data): boolean => {
      if (typeof data === 'string') {
        if (data.indexOf('http:') !== -1 || data.indexOf('https:') !== -1) {
          return true;
        }
      }
      return false;
    }),
    uniq,
    filter((url: string): boolean => url.indexOf('bk/rs/') !== -1)
  )(notPlayRounds);

  allVideoList = pipe(
    getValues,
    flatten,
    filter((data): boolean => {
      if (typeof data === 'string') {
        if (data.indexOf('http:') !== -1 || data.indexOf('https:') !== -1) {
          return true;
        }
      }
      return false;
    }),
    uniq,
    filter((url: string): boolean => url.indexOf('bk/rs/') !== -1),
    map((val: string): string => getVideoName(val))
  )(rounds);

  let mustDownloadVideoUrls: string[] = [];
  if (mustDownloadRound) {
    const [originVideoUrl] = getRoundUrls(mustDownloadRound);
    mustDownloadVideoUrls = [...mustDownloadVideoUrls, originVideoUrl];
  }

  setMustDownloadVideoUrls(mustDownloadVideoUrls);

  setNeedDownloadVideoNum(1);
  Log.i(
    DOWNLOAD_TAG,
    JSON.stringify({
      event: 'startDownloadVideos',
      mustDownloadVideoUrls,
      videoList
    })
  );
  downloadVideos(
    videoList,
    mustDownloadVideoUrls.length > 0 ? mustDownloadVideoUrls[0] : ''
  );
};

export const selectedVodProgress = (
  rollCallInfo: RollCallInfo,
  roundList: RoundInfo[],
  startRoundId: number,
  problemRoundStatus: ProblemRoundStatus
): void => {
  if (navigator.getCurrentRoute().routeName !== 'ClassRoom') {
    return;
  }
  const { lesson } = state;
  const { lessonResourceId } = lesson;

  modalOpen({
    Type: (): React.ReactElement => (
      <PortraitAlert
        title="有课程回放历史记录"
        sureDesc="继续观看"
        sureAction={(): void => {
          tracker.reportAction(startWatchClassRoomReplay, {
            objectId: lessonResourceId,
            scene: ReplayType.Continue
          });
          initRound(rollCallInfo, roundList, startRoundId, problemRoundStatus);
          modalClose();
        }}
        cancelDesc="重新开始"
        cancelAction={(): void => {
          tracker.reportAction(startWatchClassRoomReplay, {
            objectId: lessonResourceId,
            scene: ReplayType.Restart
          });
          clearRoomInfo(localLessonId);
          initRound(rollCallInfo, roundList, undefined, problemRoundStatus);
          modalClose();
        }}
      />
    ),
    needBack: true
  });
};

export const initRound = async (
  rollCallInfo: RollCallInfo,
  roundList: RoundInfo[],
  startRoundId?: number,
  problemRoundStatus?: ProblemRoundStatus
): Promise<void> => {
  downloadRoundVideo(roundList, startRoundId);
  const { rounds } = state;
  await initPPTList(rollCallInfo, rounds, startRoundId, problemRoundStatus);
};

export const checkClassRoomVodRecord = async (
  rollCallInfo: RollCallInfo,
  roundList: RoundInfo[],
  startRoundId: number
): Promise<void> => {
  const { rounds, lesson } = state;
  const { lessonResourceId } = lesson;
  if (rounds.length === 0) return;
  if (classRoomHaveVodRecordWithLessonId(localLessonId)) {
    const vodRecord = await getRoomInfo(localLessonId)(rounds[0]);
    const vodCurrentRound = vodRecord.data.currentRoundInfo;
    const isAvailableRecord =
      rounds.findIndex(
        (item): boolean => item.roundId === vodCurrentRound.roundId
      ) > -1;

    if (isAvailableRecord) {
      selectedVodProgress(
        rollCallInfo,
        roundList,
        vodCurrentRound.roundId,
        vodCurrentRound.roundStatus
      );
      return;
    }
  }
  tracker.reportAction(startWatchClassRoomReplay, {
    objectId: lessonResourceId,
    scene: ReplayType.Restart
  });
  clearRoomInfo(localLessonId);
  initRound(rollCallInfo, roundList, startRoundId);
};

// 获取课程环节列表
export const getRoundList = async (
  lessonId: number,
  lessonResourceId: number,
  startRoundId: number,
  type: ClassRoomType,
  problemRoundStatus: ProblemRoundStatus
): Promise<boolean> => {
  let error;
  let res;
  if (type === ClassRoomType.Vod) {
    [error, res] = await customAwait(
      ClassApi.getRoundListOnReview({
        lessonResourceId,
        lessonId
      })
    );
  } else {
    [error, res] = await customAwait(
      ClassApi.getRoundList({
        lessonResourceId,
        lessonId
      })
    );
  }

  if (!res || !!error || res.code !== 0) {
    fetchFailed();
    reload = (): void => {
      getRoundList(
        lessonId,
        lessonResourceId,
        startRoundId,
        type,
        problemRoundStatus
      );
    };
    return false;
  }

  const { rounds, teacherName } = res.data;
  const { teacherAudios = [] } = res.data;
  let rollCallInfo: RollCallInfo = {
    teacherAudios: [],
    rollCallUserInfo: initRollCallUserInfo
  };

  setRounds(rounds);
  setTeacherName(teacherName);

  const currentRoundIndex = rounds.findIndex(
    (round): boolean => round.roundId === startRoundId
  );
  let aimRound;
  if (currentRoundIndex > -1) {
    aimRound = rounds[currentRoundIndex];
  }
  if (aimRound && aimRound.roundType !== RoundType.RollCall) {
    const nextRoundIndex = currentRoundIndex + 1;
    if (rounds.length > nextRoundIndex) {
      aimRound = rounds[nextRoundIndex];
    }
  }
  if (
    type !== ClassRoomType.Vod &&
    aimRound &&
    aimRound.roundType === RoundType.RollCall
  ) {
    const [rollError, rollRes] = await customAwait(
      ClassApi.getPlatformStudent({
        roundId: aimRound.roundId,
        lessonId: localLessonId
      })
    );
    if (!rollRes || !!rollError || rollRes.code !== 0) {
      fetchFailed();
      reload = (): void => {
        getRoundList(
          lessonId,
          lessonResourceId,
          startRoundId,
          type,
          problemRoundStatus
        );
      };
      return false;
    }
    const {
      teacherAudios: localTeacherAudios,
      studentId,
      studentName
    } = rollRes.data;
    rollCallInfo = {
      teacherAudios: localTeacherAudios,
      rollCallUserInfo: { userName: studentName, userId: studentId },
      roundId: aimRound.roundId
    };
  }

  if (type === ClassRoomType.Vod) {
    const { userId, nickName } = getUserInfo();
    const rollCallUserInfo = { userName: nickName, userId: Number(userId) };
    rollCallInfo = {
      teacherAudios,
      rollCallUserInfo
    };
    checkClassRoomVodRecord(rollCallInfo, rounds, startRoundId);
  } else {
    initRound(rollCallInfo, rounds, startRoundId, problemRoundStatus);
  }

  return true;
};

export const uploadUserEnterNextRound = (round: RoundInfo): void => {
  const { roundId } = round;
  const params = { roundId, lessonId: localLessonId };
  Log.i(TAG, JSON.stringify({ event: 'uploadUserEnterNextRound', ...params }));

  const { type } = state;
  if (type === ClassRoomType.Vod) return;

  ClassApi.userEnterNextRound(params);
};

export const reportEvent = (reportType: ReportEvent): void => {
  const { lesson, type, showLiveEnd, showPrepare, currentPPT } = state;
  const { round: currentRound } = currentPPT;
  const { lessonId } = lesson;

  let isRestVideo = false;

  if (currentRound) {
    const { roundInfoVideo, roundType } = currentRound;
    isRestVideo =
      roundType === RoundType.Video && roundInfoVideo && roundInfoVideo.isRest;
  }

  if (
    type === ClassRoomType.Live &&
    !showLiveEnd &&
    !isRestVideo &&
    !showPrepare
  ) {
    ClassApi.userEventReport({
      lessonId,
      type: reportType
    });
  }
};

const appStateChange = (): void => {
  const { currentPPT } = state;
  const appState = AppState.currentState;

  Log.i(
    TAG,
    JSON.stringify({
      event: 'appStateChange',
      appState,
      pptType: currentPPT.type
    })
  );

  if (currentPPT.type === PPTType.Unknown) {
    return;
  }

  if (appState === 'active') {
    playPPT();
    checkClassRoomInfo();
    startCheckClassRoom();
    setVideoAvailable(true);
    reportEvent(ReportEvent.AppActive);
  } else if (appState === 'background') {
    pausePPT();
    clearCheckTimer();
    setVideoAvailable(false);
    reportEvent(ReportEvent.AppBackground);
  }
};

export const addAppStateEventListener = (): void => {
  AppState.addEventListener('change', appStateChange);
};

export const removeAppStateEventListener = (): void => {
  AppState.removeEventListener('change', appStateChange);
};

export const lessonEnd = (): void => {
  leaveAllChannel();
  setShowLiveEnd(true);
  setShowLock(false);
  pauseAllPlayPPT();
  removeListener();
  clearAllTimer();
};

export const nextRound = (): void => {
  const { currentPPT, rounds } = state;
  const { round: currentRound } = currentPPT;
  if (!currentRound) return;

  const { serialId } = currentRound;
  const currentVideoRoundIndex = rounds.findIndex(
    (round): boolean => round.serialId === serialId
  );

  const prepareRoundIndex = currentVideoRoundIndex + 1;
  if (rounds.length <= prepareRoundIndex) {
    lessonEnd();
    return;
  }

  const round = rounds[prepareRoundIndex];
  const { roundType } = round;
  const showProblem = roundType === RoundType.Problem;
  const showRollCall = roundType === RoundType.RollCall;

  uploadUserEnterNextRound(round);
  nextPPT();

  if (showProblem || showRollCall) {
    uploadUserProblemRoundProgress(
      round,
      ProblemRoundStatus.DoProblemAfterDiscuss
    );
  }
};

export const onEnd = (): void => {
  Log.i(TAG, JSON.stringify({ event: 'onEnd' }));
  const { currentPPT } = state;
  const { videoType, round } = currentPPT;
  if (videoType === VideoType.RollCall && round) {
    nextPPT();
    uploadUserProblemRoundProgress(round, ProblemRoundStatus.Analysis);
    return;
  }

  nextRound();
};

export const updateImpromptuInfo = (currentTime: number): void => {
  const { currentPPT } = state;
  const { round: currentRound } = currentPPT;
  if (!currentRound) return;
  const { roundType, roundInfoVideo, roundInfoProblem } = currentRound;

  // 点名即兴问答不走通用逻辑
  if (roundType === RoundType.RollCall) return;

  let localInstantAnswers: InstantAnswer[] = [];
  if (roundType === RoundType.Video && roundInfoVideo) {
    const { instantAnswers = [] } = roundInfoVideo;
    localInstantAnswers = instantAnswers;
  } else if (roundType === RoundType.Problem && roundInfoProblem) {
    const { instantAnswers = [] } = roundInfoProblem;
    localInstantAnswers = instantAnswers;
  }

  if (localInstantAnswers.length === 0) {
    return;
  }

  for (let i = 0; i < localInstantAnswers.length; i += 1) {
    const instantAnswer = localInstantAnswers[i];
    if (
      instantAnswer.startTime > currentTime &&
      instantAnswer.startTime - currentTime < 1000
    ) {
      const { instantAnswerInfo } = state;
      const { show, instantAnswer: localInstantAnswer } = instantAnswerInfo;
      const { instantAnswerId: localInstantAnswerId } = localInstantAnswer;
      const { instantAnswerId } = instantAnswer;
      if (localInstantAnswerId === instantAnswerId && show) {
        return;
      }
      setInstantAnswerInfo({ show: true, instantAnswer });
      break;
    }
  }
};

// 视频进度回调
export const onProgress = (e: Progress): void => {
  const { currentPPT, type } = state;
  const { round: currentRound, videoType } = currentPPT;
  if (!currentRound) return;

  if (e.currentTime > MIN_PREPARE_ROLL_CALL_TIME) {
    prepareRollCallInfo();
  }
  setCurrentTime(e.currentTime);
  updateImpromptuInfo(e.currentTime);

  if (type !== ClassRoomType.Vod) return;
  const {
    roundId,
    roundType,
    aboveTotalTime,
    roundInfoProblem,
    roundInfoRollCall
  } = currentRound;
  if (videoType === VideoType.RollCall) {
    setClassRoomCurrentTime(e.currentTime + aboveTotalTime);
    updateCurrentRoundInfo(
      localLessonId,
      type
    )({
      roundId,
      roundType,
      doProblemProgress: e.currentTime,
      roundStatus: ProblemRoundStatus.DoProblemAfterDiscuss
    });
  } else if (
    videoType === VideoType.SubmitRight ||
    videoType === VideoType.SubmitWrong
  ) {
    const { rollCallVideoDuration } = roundInfoRollCall;
    setClassRoomCurrentTime(
      e.currentTime + aboveTotalTime + rollCallVideoDuration
    );
    updateCurrentRoundInfo(
      localLessonId,
      type
    )({
      roundId,
      roundType,
      explainVideoProgress: e.currentTime,
      roundStatus: ProblemRoundStatus.Analysis
    });
  } else if (roundType === RoundType.Video) {
    setClassRoomCurrentTime(e.currentTime + aboveTotalTime);
    updateCurrentRoundInfo(
      localLessonId,
      type
    )({
      roundId,
      roundType,
      roundProgress: e.currentTime
    });
  } else {
    const { answerDuration } = roundInfoProblem;
    setClassRoomCurrentTime(e.currentTime + aboveTotalTime + answerDuration);
    updateCurrentRoundInfo(
      localLessonId,
      type
    )({
      roundId,
      roundType,
      explainVideoProgress: e.currentTime,
      roundStatus: ProblemRoundStatus.Analysis
    });
  }
};

export const startReceiveFinishProblemMsg = (): void => {
  finishProblemSubscription = DeviceEventEmitter.addListener(
    CLASS_FINISH_PROBLEM,
    (data: FinishProblemData): void => {
      const { userId, goldCoinNum, stageId, problemId, sceneIds } = data;

      Log.i(
        TAG,
        JSON.stringify({ event: 'startReceiveFinishProblemMsg', ...data })
      );

      if (sceneIds.length > 0) {
        haveTeacherAudio = true;
      }

      let { users } = state;
      const { currentPPT } = state;
      const { round: currentRound } = currentPPT;

      // 环节不匹配
      if (currentRound && currentRound.stageId !== stageId) return;

      const { problem, type } = currentPPT;

      // 当前显示的不是题部分
      if (type !== PPTType.Problem) return;

      if (problem) {
        const { problemId: currentProblemId } = problem;

        // 题不匹配
        if (currentProblemId !== problemId) return;
      }

      // 收到自己的答题完成消息，不做处理。因为提交答案后已经做过处理，会跳题
      const { userId: localUserId } = getLocalUserInfo();
      if (String(localUserId) === String(userId)) return;

      users = users.map(
        (user): LiveUser => {
          if (Number(user.userId) === Number(userId) && !user.haveAnswer) {
            const addGoldCoinNum = goldCoinNum - user.goldCoinNum;
            emitSubmitSuccess({
              type: 'problem',
              addGoldCoinNum,
              userId: Number(userId)
            });
            return {
              ...user,
              goldCoinNum,
              haveAnswer: true
            };
          }
          return user;
        }
      );
      setUsers(users);
      checkUserAnswer(users);
    }
  );
};

export const stopReceiveFinishProblemMsg = (): void => {
  if (finishProblemSubscription) {
    finishProblemSubscription.remove();
  }
};

export const getNeedOpenMicrophone = (): boolean => {
  const { showPrepare, currentPPT } = state;
  const { round: currentRound } = currentPPT;

  let isRestVideo = false;

  if (currentRound) {
    const { roundInfoVideo, roundType } = currentRound;
    isRestVideo =
      roundType === RoundType.Video && roundInfoVideo && roundInfoVideo.isRest;
  }
  const needOpenMicrophone = showPrepare || isRestVideo;
  return needOpenMicrophone;
};

export const startReceiveFinishInstant = (): void => {
  finishInstantSubscription = DeviceEventEmitter.addListener(
    CLASS_FINISH_INSTANT,
    (data: FinishInstantData): void => {
      const { userId, goldCoinNum } = data;
      Log.i(
        TAG,
        JSON.stringify({ event: 'startReceiveFinishInstantMsg', ...data })
      );

      const { userId: localUserId } = getLocalUserInfo();
      if (String(localUserId) === String(userId)) return;

      let { users } = state;
      users = users.map(
        (user): LiveUser => {
          if (user.userId === String(userId)) {
            const addGoldCoinNum = goldCoinNum - user.goldCoinNum;
            emitSubmitSuccess({
              type: 'instant',
              addGoldCoinNum,
              userId: Number(userId)
            });
            return {
              ...user,
              goldCoinNum
            };
          }
          return user;
        }
      );
      setUsers(users);
    }
  );
};

export const stopReceiveFinishInstant = (): void => {
  if (finishInstantSubscription) {
    finishInstantSubscription.remove();
  }
};

export const startReceiveUserJoinRoomMsg = (): void => {
  userJoinRoomSubscription = DeviceEventEmitter.addListener(
    CLASS_USER_JOIN_ROOM,
    (data: UserJoinRoomData): void => {
      Log.i(
        TAG,
        JSON.stringify({ event: 'startReceiveUserJoinRoomMsg', ...data })
      );
      getLiveUserInfo(data);
    }
  );
};

export const stopReceiveUserJoinRoomMsg = (): void => {
  if (userJoinRoomSubscription) {
    userJoinRoomSubscription.remove();
  }
};

export const lessonSignOrShowFaceChangeGoldCoinNumber = (
  data: LessonSignData
): void => {
  const { userId, goldCoinChangeNum } = data;
  const { userId: localUserId } = getLocalUserInfo();
  if (Number(userId) === Number(localUserId)) return;

  let { users } = state;
  users = users.map(
    (user): LiveUser => {
      if (Number(user.userId) === Number(userId)) {
        return {
          ...user,
          goldCoinNum: user.goldCoinNum + goldCoinChangeNum
        };
      }
      return user;
    }
  );
  setUsers(users);
};

export const startReceiveLessonSignMsg = (): void => {
  lessonSignSubscription = DeviceEventEmitter.addListener(
    CLASS_LESSON_SIGN,
    (data: LessonSignData): void => {
      Log.i(
        TAG,
        JSON.stringify({ event: 'startReceiveLessonSignMsg', ...data })
      );
      lessonSignOrShowFaceChangeGoldCoinNumber(data);
    }
  );
};

export const stopReceiveLessonSignMsg = (): void => {
  if (lessonSignSubscription) {
    lessonSignSubscription.remove();
  }
};

export const startReceiveShowFaceMsg = (): void => {
  showFaceSubscription = DeviceEventEmitter.addListener(
    CLASS_SHOW_FACE,
    (data: ShowFaceData): void => {
      Log.i(TAG, JSON.stringify({ event: 'startReceiveShowFaceMsg', ...data }));
      lessonSignOrShowFaceChangeGoldCoinNumber(data);
    }
  );
};

export const stopReceiveShowFaceMsg = (): void => {
  if (showFaceSubscription) {
    showFaceSubscription.remove();
  }
};

const updateSoundUrlInfo = async (audios: TeacherAudio[]): Promise<void> => {
  if (audios.length === 0) return;
  let audioList: TeacherAudio[] = [];
  for (let i = 0; i < audios.length; i += 1) {
    const audio = audios[i];
    const { originUrls, url } = audio;
    if (originUrls && originUrls.length > 0) {
      // eslint-disable-next-line no-await-in-loop
      const [error, res] = await customAwait(spliceAudio(originUrls));
      if (!error && !!res) {
        const { audioUrl, audioFilePath, duration } = res;
        Log.i(TAG, JSON.stringify({ event: 'spliceAudio success : ', res }));
        audioList = [
          ...audioList,
          { ...audio, url: audioUrl, audioFilePath, duration }
        ];
      } else {
        Log.e(TAG, JSON.stringify({ event: 'spliceAudio error', error }));
      }
    } else if (url && url.length > 0) {
      audioList = [...audioList, audio];
    }
  }
  setSoundUrlInfo(audioList);
};

export const startReceiveTeacherAudioMsg = (): void => {
  teacherAudioSubscription = DeviceEventEmitter.addListener(
    CLASS_TEACHER_AUDIO,
    (data: TeacherAudioData): void => {
      Log.i(
        TAG,
        JSON.stringify({ event: 'startReceiveTeacherAudioMsg', ...data })
      );
      const { currentPPT } = state;
      const { roundId } = currentPPT;

      // 语音消息携带的roundId为0则不做限制，都可以播放，如果不为0，则需要和当前环节id做比较
      const validTeacherAudios = data.teacherAudios.filter(
        (audio): boolean => audio.roundId === 0 || audio.roundId === roundId
      );
      updateSoundUrlInfo(validTeacherAudios);
    }
  );
};

export const stopReceiveTeacherAudioMsg = (): void => {
  if (teacherAudioSubscription) {
    teacherAudioSubscription.remove();
  }
};

export const startReceiveFaceChangeMsg = (): void => {
  let haveSendLog = false;
  let sendLogTimer: NodeJS.Timer | null = null;
  faceChangeSubscription = DeviceEventEmitter.addListener(
    REACT_NATIVE_FACE_CHANGE,
    ({ faceNumber }): void => {
      // 接收人脸识别的回调，5s打一次日志
      if (!haveSendLog) {
        haveSendLog = true;
        Log.i(
          TAG,
          JSON.stringify({ event: 'startReceiveFaceChangeMsg', faceNumber })
        );
      } else if (sendLogTimer === null) {
        sendLogTimer = setTimeout((): void => {
          haveSendLog = false;
          sendLogTimer = null;
        }, 5000);
      }
      updateFaces(faceNumber);
    }
  );
};

export const stopReceiveFaceChangeMsg = (): void => {
  if (faceChangeSubscription) {
    faceChangeSubscription.remove();
  }
};

export const startReceiveUserLeaveRoomMsg = (): void => {
  userLeaveRoomSubscription = DeviceEventEmitter.addListener(
    CLASS_USER_LEAVE_ROOM,
    (data: UserLeaveRoomData): void => {
      const { userId: leaveUserId } = data;
      Log.i(
        TAG,
        JSON.stringify({ event: 'startReceiveUserLeaveRoomMsg', ...data })
      );
      const { users } = state;
      const leaveUserIndex = findIndex(propEq('userId', String(leaveUserId)))(
        users
      );

      if (leaveUserIndex > -1) {
        setUsers(remove(leaveUserIndex, 1, users));
      }
    }
  );
};

export const stopReceiveUserLeaveRoomMsg = (): void => {
  if (userLeaveRoomSubscription) {
    userLeaveRoomSubscription.remove();
  }
};

export const startReceiveLessonStartMsg = (): void => {
  lessonStartSubscription = DeviceEventEmitter.addListener(
    CLASS_LESSON_START,
    (): void => {
      Log.i(TAG, JSON.stringify({ event: 'startReceiveLessonStartMsg' }));
      clearBeforeLessonStartTimer();
      lessonStart();
    }
  );
};

export const startListenDownloadFail = (): void => {
  failSubscription = DeviceEventEmitter.addListener(
    DOWNLOAD_VIDEO_TASK_FAIL,
    (task: DownloadVideoTaskProps): void => {
      Log.i(
        TAG,
        JSON.stringify({
          event: 'downloadResourceFail',
          urls: task.urls
        })
      );
      startDownload(task.id);
    }
  );
};

export const stopListenDownloadFail = (): void => {
  if (failSubscription) {
    failSubscription.remove();
  }
};

export const getLocalVideoUrl = (
  key: string,
  type: GetLocalVideoUrlType
): string => {
  const { lessonResourceId } = state.lesson;
  const tasks = getVideoTasksByParentId(lessonResourceId);
  const task = tasks[`${lessonResourceId}-${getVideoName(key)}`];
  if (!task) {
    Log.i(
      TAG,
      JSON.stringify({ event: 'getLocalVideoUrl no video', key, tasks })
    );
    tracker.reportAction(getVideoInClassRoomFailed, {
      reason: JSON.stringify({ key, tasks })
    });
    return '';
  }

  if (!getVideoTaskIsFinished(task)) {
    Log.i(
      TAG,
      JSON.stringify({
        event: 'getLocalVideoUrl not finished',
        key,
        task
      })
    );
    tracker.reportAction(getVideoInClassRoomUnfinished, {
      reason: JSON.stringify({ key, task })
    });
    // 边下边播
    // return '';
  }

  const m3u8Path = task.m3u8Paths[type];

  const paths = split('videos', m3u8Path);

  Log.i(
    TAG,
    JSON.stringify({
      event: 'getLocalVideoUrl res',
      key,
      task,
      paths,
      m3u8Path,
      DOWNLOAD_PATH
    })
  );

  if (paths.length > 1) {
    return `http://127.0.0.1:21994/videos${paths[1]}`;
  }

  return m3u8Path;
};

export const onPressNextRound = (): void => {
  const round = getNextRound();
  Log.i(TAG, JSON.stringify({ event: 'onPressNextRound mainVideo', round }));
  onEnd();
};

export const reloadAction = (): void => {
  reload();
};

export const setSelectedAnswer = (answer: ProblemAnswer): void => {
  const { currentPPT } = state;
  if (currentPPT && currentPPT.round) {
    currentPPT.round.roundInfoProblem.userAnswer = answer;
    setCurrentPPT(currentPPT);
  }
};

export const updateProblemTime = (second: number): void => {
  problemRemainTime = second;
  const { currentPPT, type } = state;
  const { round: currentRound } = currentPPT;
  if (!currentRound) return;

  const { roundInfoProblem, roundType, roundId, aboveTotalTime } = currentRound;
  if (!roundInfoProblem) return;

  const { answerDuration } = roundInfoProblem;
  const doProblemProgress = answerDuration - second * 1000;

  if (type !== ClassRoomType.Vod) {
    if (doProblemProgress > MIN_PREPARE_ROLL_CALL_TIME) {
      prepareRollCallInfo();
    }
    return;
  }

  updateCurrentRoundInfo(
    localLessonId,
    type
  )({
    roundId,
    roundType,
    doProblemProgress,
    roundStatus: ProblemRoundStatus.DoProblemAfterDiscuss
  });

  setClassRoomCurrentTime(aboveTotalTime + doProblemProgress);
};

export const uploadUserInstantAnswer = async (
  answer: string,
  useTime: number,
  instantAnswerInfo?: InstantAnswerInfo
): Promise<InstantRes> => {
  const { currentPPT, instantAnswerInfo: localInstantAnswerInfo, type } = state;
  const { roundId } = currentPPT;

  const { instantAnswer } = instantAnswerInfo || localInstantAnswerInfo;
  const { instantAnswerId, duration } = instantAnswer;
  const params = {
    roundId,
    lessonId: localLessonId,
    instantAnswerId,
    answer,
    useTime: duration - useTime * 1000
  };

  let res: UploadUserInstantAnswerRes;
  if (type === ClassRoomType.Live) {
    res = await ClassApi.uploadUserInstantAnswer(params);
  } else {
    res = await ClassApi.submitReplayInstant(params);
  }

  const { code, data } = res;
  if (code !== 0) return {};

  const { isCorrect } = data;
  if (isCorrect) {
    let { users } = state;
    users = users.map(
      (user): LiveUser => {
        const { userId } = getLocalUserInfo();
        if (Number(user.userId) === Number(userId)) {
          updateUserGoldCoinNum(localLessonId, type)(user.goldCoinNum + 1);
          emitSubmitSuccess({
            type: 'instant',
            addGoldCoinNum: 1,
            userId: Number(userId)
          });
          return {
            ...user,
            goldCoinNum: user.goldCoinNum + 1
          };
        }
        return user;
      }
    );
    setUsers(users);
  }

  return { needShowRes: true, isCorrect };
};

export const clearAllFinishProblemTimer = (): void => {
  if (allFinishProblemTimer) {
    clearTimeout(allFinishProblemTimer);
    allFinishProblemTimer = null;
  }
};

export const startDownloadFailTimer = (): void => {
  checkDownloadFailTimer = setInterval((): void => {
    const { lessonResourceId } = state.lesson;
    const tasks = getVideoTasksByParentId(lessonResourceId);
    let failedTaskId: string | number = '';
    Object.values(tasks).forEach((task): void => {
      if (task.isFailed) {
        failedTaskId = task.id;
      }
    });
    if (failedTaskId) {
      startDownload(failedTaskId);
    }
  }, 10 * 1000);
};

export const clearCheckDownloadFailTimer = (): void => {
  if (checkDownloadFailTimer) {
    clearInterval(checkDownloadFailTimer);
    checkDownloadFailTimer = null;
  }
};

export const addListener = (type: ClassRoomType): void => {
  if (type === ClassRoomType.Live) {
    startReceiveFinishProblemMsg();
    startReceiveUserJoinRoomMsg();
    startReceiveUserLeaveRoomMsg();
    startReceiveLessonStartMsg();
    startReceiveFinishInstant();
    addAppStateEventListener();
    startReceiveShowFaceMsg();
    startReceiveLessonSignMsg();
    startReceiveTeacherAudioMsg();
  }
  startDownloadFailTimer();
  startListenDownloadFail();
  startReceiveFaceChangeMsg();
};

export const removeListener = (): void => {
  stopReceiveFinishProblemMsg();
  stopReceiveUserJoinRoomMsg();
  stopReceiveUserLeaveRoomMsg();
  stopReceiveLessonStartMsg();
  stopListenDownloadFail();
  removeAppStateEventListener();
  stopReceiveFinishInstant();
  clearCheckDownloadFailTimer();
  stopReceiveShowFaceMsg();
  stopReceiveLessonSignMsg();
  stopReceiveTeacherAudioMsg();
  stopReceiveFaceChangeMsg();
};

export const clearAllTimer = (): void => {
  clearCheckTimer();
  clearBeforeLessonStartTimer();
  clearFaceTimer();
  clearReplayTimer();
  clearAllFinishProblemTimer();
  clearBarStatusChangeTimer();
  clearMicrophoneTimer();
  clearCoinProgressTimer();
};

export const getAgoraTokenAndRoundList = async (
  props: ClassRoomProps
): Promise<void> => {
  const {
    lessonId,
    channel,
    type,
    lessonResourceId,
    startRoundId,
    roundStatus
  } = props;
  const res = await getAgoraToken(channel, type);
  if (!res) {
    fetchFailed();
    reload = (): void => {
      getAgoraTokenAndRoundList(props);
    };
    return;
  }
  getRoundList(lessonId, lessonResourceId, startRoundId, type, roundStatus);
};

// 获取家长账号对应的学生id
export const getParentStudentId = async (): Promise<void> => {
  const res = await ClassApi.getStudentOfParent();
  const { code, data } = res;
  if (code === 0) {
    const { studentId } = data;
    setParentStudentId(String(studentId));
  }
};

export const initClassRoom = (props: ClassRoomProps): void => {
  const { lessonId, type } = props;
  const { role } = getUserInfo();
  if (role === UserRole.Parent && type === ClassRoomType.Live) {
    getParentStudentId();
  }
  initClassRoomData(props);
  setLocalLessonId(lessonId);
  tracker.reportAction(showClassRoom, {
    objectId: lessonId,
    videoPlayType: type === ClassRoomType.Live ? 'live' : 'vod'
  });
  getAgoraTokenAndRoundList(props);
};

export const reportVideoPlayerOnError = async (
  reason: string,
  videoUrl: string
): Promise<void> => {
  const { type } = state;
  tracker.reportAction(videoPlayerError, {
    reason,
    videoPlayType: type === ClassRoomType.Live ? 'live' : 'vod'
  });

  let needDeleteVideoCache = true;
  try {
    const reasonData = JSON.parse(reason);
    if (reasonData) {
      const { error } = reasonData;
      if (error && error.error && error.error.code) {
        // Could not  to the server. 不需要清该视频缓存
        needDeleteVideoCache = error.error.code !== -1004;
      }
    }
  } catch (error) {
    Log.i(
      TAG,
      JSON.stringify({
        event: 'reportVideoPlayerOnError json parse error',
        error
      })
    );
  }

  if (!needDeleteVideoCache) {
    return;
  }

  const { lesson } = state;
  const { lessonResourceId } = lesson;
  modalOpen({
    Type: (): ReactElement => (
      <Alert
        title="温馨提示"
        subTitle="课程加载慢，请退出教室重试"
        sureDesc="确定"
        sureAction={(): void => {
          deleteOtherVideoTask(`${lessonResourceId}-${getVideoName(videoUrl)}`);

          back();
          modalClose();
        }}
      />
    ),
    needBack: true
  });
};

export const showOpenMicrophone = (): void => {
  const { type } = state;
  if (type === ClassRoomType.Vod) return;

  setShowOpenMicrophone(true);

  setTimeout((): void => {
    setShowOpenMicrophone(false);
  }, 1500);
};

export const getNextRound = (): RoundInfo | null => {
  const { rounds, currentPPT } = state;
  const { round: currentRound } = currentPPT;
  if (!currentRound) return null;

  const { serialId } = currentRound;
  const currentVideoRoundIndex = rounds.findIndex(
    (round): boolean => round.serialId === serialId
  );
  const prepareRoundIndex = currentVideoRoundIndex + 1;
  if (rounds.length <= prepareRoundIndex) {
    return null;
  }

  const round = rounds[prepareRoundIndex];
  return round;
};

export const ignoreDownloadTask = (url: string, time: number): void => {
  const { lessonResourceId } = state.lesson;
  const tasks = getAllStoreVideoTasks();
  const taskId = `${lessonResourceId}-${getVideoName(url)}`;
  const ignoreTsList: string[] = [];

  let tsNum = 1;
  const currentDownloadTask = getCurrentDownloadVideoTask();

  Log.i(
    TAG,
    JSON.stringify({
      event: 'ignoreDownloadTask',
      url,
      time,
      taskId,
      currentDownloadTaskId: currentDownloadTask ? currentDownloadTask.id : ''
    })
  );

  // 要播放的视频下载任务没有下完，直接跳ts即可
  if (currentDownloadTask && currentDownloadTask.id === taskId) {
    tsNum = Math.max(Math.floor(time / 10000) - 1, 0);
    Log.i(
      TAG,
      JSON.stringify({
        event: 'ignoreDownloadTaskDownloading',
        taskId,
        tsNum
      })
    );
    for (let i = 1; i <= tsNum; i += 1) {
      ignoreTsList.push(`00000${i}`.slice(-5));
    }
    currentDownloadTask.setIgnoreTsList(ignoreTsList);
  } else {
    // 要播放的视频还没有开始下载
    tsNum = Math.max(Math.floor(time / 10000) - 1, 0);
    Log.i(
      TAG,
      JSON.stringify({
        event: 'ignoreDownloadTaskUnStart',
        taskId,
        tsNum
      })
    );

    const currentVideoSeq = allVideoList.findIndex((item): boolean =>
      taskId.includes(item)
    );
    for (let i = 0; i < tasks.length; i += 1) {
      if (tasks[i].parentId === lessonResourceId) {
        const seq = allVideoList.findIndex((item): boolean =>
          String(tasks[i].id).includes(item)
        );
        if (seq !== -1 && seq < currentVideoSeq) {
          setUnStartVideoTaskPaused(tasks[i].id);
        } else if (seq >= currentVideoSeq) {
          setUnStartVideoTaskDownload(tasks[i].id);
        }
      }
    }

    for (let i = 1; i <= tsNum; i += 1) {
      ignoreTsList.push(`00000${i}`.slice(-5));
    }
    pauseDownloadVideoTask(); // 暂停当前的下载任务
    setUnStartVideoTaskDownload(taskId);
    setTimeout((): void => {
      continueDownload(ignoreTsList);
    }, 150);
  }
};

export const initPPTList = (
  rollCallInfo: RollCallInfo,
  rounds: RoundInfo[],
  startRoundId?: number,
  problemRoundStatus?: ProblemRoundStatus
): void => {
  Log.i(TAG, JSON.stringify({ event: 'initPPTList', problemRoundStatus }));

  let startRoundIdIndex = rounds.findIndex(
    (round): boolean => round.roundId === startRoundId
  );
  if (startRoundIdIndex < 0) {
    startRoundIdIndex = 0;
  }
  const { type } = state;

  const notPlayRounds = rounds.filter(
    (r, index): boolean =>
      type === ClassRoomType.Vod || index >= startRoundIdIndex
  );

  const PPTList = pipe(
    map((round: RoundInfo): PPT[] => {
      if (round.roundType === RoundType.Video) {
        const ppt = {
          type: PPTType.Video,
          videoType: VideoType.Main,
          index: 0,
          roundId: round.roundId,
          url: round.roundInfoVideo.video.originVideoDownloadURL,
          status: PPTStatus.Unknown,
          round,
          lessonId: localLessonId
        };
        return [ppt];
      }
      if (round.roundType === RoundType.RollCall) {
        let { teacherAudios, rollCallUserInfo } = rollCallInfo;
        const { roundId } = rollCallInfo;
        if (roundId && roundId !== round.roundId) {
          teacherAudios = [];
          rollCallUserInfo = initRollCallUserInfo;
        }
        const rollCallAudio = teacherAudios.find(
          (audio): boolean => Number(audio.sceneId) === AUDIO_SCENE_ID.RollCall
        );
        const submitRightAudio = teacherAudios.find(
          (audio): boolean =>
            Number(audio.sceneId) === AUDIO_SCENE_ID.SubmitRight
        );
        const submitWrongAudio = teacherAudios.find(
          (audio): boolean =>
            Number(audio.sceneId) === AUDIO_SCENE_ID.SubmitWrong
        );
        const { roundInfoRollCall } = round;
        const {
          rollCallVideo,
          rightVideo,
          wrongVideo,
          instantAnswers
        } = roundInfoRollCall;
        const rollCallPPT: PPT = {
          type: PPTType.Video,
          videoType: VideoType.RollCall,
          index: 0,
          roundId: round.roundId,
          url: rollCallVideo.originVideoDownloadURL,
          status: PPTStatus.Unknown,
          round,
          instantAnswer:
            instantAnswers.length > 0 ? instantAnswers[0] : undefined,
          audio: rollCallAudio,
          rollCallUserInfo,
          lessonId: localLessonId
        };
        const rightVideoPPT: PPT = {
          type: PPTType.Video,
          videoType: VideoType.SubmitRight,
          index: 0,
          roundId: round.roundId,
          url: rightVideo.originVideoDownloadURL,
          status: PPTStatus.Unknown,
          round,
          audio: submitRightAudio,
          rollCallUserInfo,
          lessonId: localLessonId
        };
        const wrongVideoPPT: PPT = {
          type: PPTType.Video,
          videoType: VideoType.SubmitWrong,
          index: 0,
          roundId: round.roundId,
          url: wrongVideo.originVideoDownloadURL,
          status: PPTStatus.Unknown,
          round,
          audio: submitWrongAudio,
          rollCallUserInfo,
          lessonId: localLessonId
        };
        return [rollCallPPT, rightVideoPPT, wrongVideoPPT];
      }
      if (round.roundType === RoundType.Problem) {
        const { commonExplainVideo, problem } = round.roundInfoProblem;
        const { originVideoDownloadURL } = commonExplainVideo;
        let res: PPT[] = [];
        if (
          type === ClassRoomType.Vod ||
          startRoundId !== round.roundId ||
          (startRoundId === round.roundId &&
            problemRoundStatus &&
            problemRoundStatus === ProblemRoundStatus.DoProblemAfterDiscuss)
        ) {
          const ppt: PPT = {
            type: PPTType.Problem,
            index: 0,
            roundId: round.roundId,
            problem,
            status: PPTStatus.Unknown,
            round,
            lessonId: localLessonId
          };
          res = [...res, ppt];
        }
        if (originVideoDownloadURL !== '') {
          const ppt = {
            type: PPTType.Video,
            videoType: VideoType.Analysis,
            index: 0,
            roundId: round.roundId,
            url: originVideoDownloadURL,
            status: PPTStatus.Unknown,
            round,
            lessonId: localLessonId
          };
          res = [...res, ppt];
        }
        return res;
      }
      return [];
    }),
    flatten,
    (list: PPT[]): PPT[] =>
      list.map(
        (ppt: PPT, index): PPT => ({
          ...ppt,
          index
        })
      )
  )(notPlayRounds);

  setPPTList(PPTList);
};

export const setPPTPlayer = (player: any, index: number): void => {
  if (pptPlayer[`index-${index}`] || !player) return;
  pptPlayer[`index-${index}`] = player;
  const { PPTList, currentPPT } = state;
  Log.i(TAG, JSON.stringify({ event: 'setPPTPlayer', pptPlayer }));
  if (keys(pptPlayer).length === PPTList.length) {
    preparePPT(currentPPT);
  }
};

export const updatePPTListWithPPT = (ppt: PPT): void => {
  const { PPTList, rounds } = state;
  const currentRound = rounds.find(
    (round): boolean => round.roundId === ppt.roundId
  );
  setPPTList(
    update(
      ppt.index,
      {
        ...ppt,
        round: currentRound
      },
      PPTList
    )
  );
};

export const preparePPT = async (ppt: PPT): Promise<void> => {
  const { index, status } = ppt;
  const currentPPTPlayer = pptPlayer[`index-${index}`];
  Log.i(TAG, JSON.stringify({ event: 'preparePPT', ppt }));
  if (!currentPPTPlayer) {
    Log.e(TAG, JSON.stringify({ event: 'preparePPT error', currentPPTPlayer }));
    return;
  }
  if (status === PPTStatus.Prepare) {
    Log.i(TAG, JSON.stringify({ event: 'preparePPT havePrepare', ppt }));
    return;
  }

  await currentPPTPlayer.prepare();

  updatePPTListWithPPT({ ...ppt, status: PPTStatus.Prepare });
};

export const destroyWithPPT = async (ppt: PPT): Promise<void> => {
  const { index } = ppt;
  const currentPPTPlayer = pptPlayer[`index-${index}`];
  Log.i(
    TAG,
    JSON.stringify({
      event: 'destroyWithPPT',
      type: ppt.type,
      index: ppt.index,
      roundId: ppt.roundId
    })
  );
  if (!currentPPTPlayer) {
    Log.e(
      TAG,
      JSON.stringify({ event: 'destroyWithPPT error', currentPPTPlayer })
    );
    return;
  }
  await currentPPTPlayer.destroy();
};

export const pauseWithPPT = async (ppt: PPT): Promise<void> => {
  const { index } = ppt;
  const currentPPTPlayer = pptPlayer[`index-${index}`];
  Log.i(TAG, JSON.stringify({ event: 'pauseWithPPT', ppt }));
  if (!currentPPTPlayer) {
    Log.e(
      TAG,
      JSON.stringify({ event: 'pauseWithPPT error', currentPPTPlayer })
    );
    return;
  }
  await currentPPTPlayer.pause();
};

export const pausePPT = async (): Promise<void> => {
  const { currentPPT: ppt } = state;
  const { index } = ppt;
  const currentPPTPlayer = pptPlayer[`index-${index}`];
  Log.i(TAG, JSON.stringify({ event: 'pausePPT' }));
  if (!currentPPTPlayer) {
    Log.e(TAG, JSON.stringify({ event: 'pausePPT error', currentPPTPlayer }));
    return;
  }
  await currentPPTPlayer.pause();

  updatePPTListWithPPT({ ...ppt, status: PPTStatus.Pause });
};

export const mutePPT = (mute = false): void => {
  const { currentPPT: ppt } = state;
  const { index } = ppt;
  const currentPPTPlayer = pptPlayer[`index-${index}`];
  Log.i(TAG, JSON.stringify({ event: 'mutePPT' }));
  if (!currentPPTPlayer) {
    Log.e(TAG, JSON.stringify({ event: 'mutePPT error', currentPPTPlayer }));
    return;
  }
  setIsMute(mute);
  currentPPTPlayer.mute(mute);
};

export const playPPT = async (): Promise<void> => {
  const { currentPPT: ppt, showSignIn } = state;
  const { index } = ppt;
  const currentPPTPlayer = pptPlayer[`index-${index}`];
  Log.i(TAG, JSON.stringify({ event: 'playPPT' }));
  if (!currentPPTPlayer) {
    Log.e(TAG, JSON.stringify({ event: 'playPPT error', currentPPTPlayer }));
    return;
  }
  await currentPPTPlayer.play(showSignIn);

  updatePPTListWithPPT({ ...ppt, status: PPTStatus.Play });
};

export const destroyAllNotPlayPPT = async (): Promise<void> => {
  let { PPTList } = state;
  const list = PPTList.filter(
    (p): boolean =>
      p.status !== PPTStatus.Play &&
      p.status !== PPTStatus.Unknown &&
      p.status !== PPTStatus.Destroy
  );

  for (let i = 0; i < list.length; i += 1) {
    const ppt = list[i];
    // eslint-disable-next-line no-await-in-loop
    await destroyWithPPT(ppt);
    PPTList = PPTList.map(
      (p): PPT => {
        if (p.index === ppt.index) {
          return {
            ...p,
            status: PPTStatus.Destroy
          };
        }
        return p;
      }
    );
  }

  if (list.length > 0) {
    setPPTList(PPTList);
  }
};

export const pauseAllPlayPPT = async (): Promise<void> => {
  let { PPTList } = state;
  const list = PPTList.filter((p): boolean => p.status === PPTStatus.Play);

  for (let i = 0; i < list.length; i += 1) {
    const ppt = list[i];
    // eslint-disable-next-line no-await-in-loop
    await pauseWithPPT(ppt);
    PPTList = PPTList.map(
      (p): PPT => {
        if (p.index === ppt.index) {
          return {
            ...p,
            status: PPTStatus.Pause
          };
        }
        return p;
      }
    );
  }

  if (list.length > 0) {
    setPPTList(PPTList);
  }
};

export const startPPT = async ({
  ppt,
  currentRoundInfo
}: StartPPTProps): Promise<void> => {
  // 开始新ppt的时候，确认长链接状态
  shellExec.makeSureLongLinkConnected();

  const { PPTList, currentPPT, showDownloadResource, needSign, isMute } = state;
  const { index, round } = ppt;
  const currentPPTPlayer = pptPlayer[`index-${index}`];
  Log.i(TAG, JSON.stringify({ event: 'startPPT', ppt }));
  if (!currentPPTPlayer) {
    Log.e(
      TAG,
      JSON.stringify({ event: 'startPPT no pptPlayer', currentPPTPlayer })
    );
    return;
  }

  if (haveSeek) {
    Log.e(TAG, JSON.stringify({ event: 'startPPT haveSeek' }));
    return;
  }

  haveSeek = true;

  if (currentPPT.index === index && currentRoundInfo) {
    await syncPPT(currentRoundInfo, false);
    haveSeek = false;
    nextSeekTime();
    return;
  }

  let needShowLoading = true;
  setTimeout((): void => {
    if (needShowLoading) {
      setShowLoading(true);
    }
  }, 500);

  await pauseAllPlayPPT();
  await currentPPTPlayer.start(round, isMute);

  if (showDownloadResource) {
    setShowDownloadResource(false);
    if (needSign) {
      setShowSignIn(true);
    } else {
      startCountCoinProgress();
    }
  }

  needShowLoading = false;
  setShowLoading(false);
  setCurrentPPT(ppt);
  await pptScrollTo(ppt.index);

  const { type } = currentPPT;
  if (type === PPTType.Problem) {
    clearUserAnswerInfo();
  }

  // 切换ppt的时候需要清空语音消息
  setSoundUrl(undefined);
  setSoundUrlList([]);
  haveTeacherAudio = false;

  updatePPTListWithPPT({ ...ppt, status: PPTStatus.Play });
  await destroyAllNotPlayPPT();
  haveSeek = false;
  nextSeekTime();

  if (index === PPTList.length - 1) {
    Log.i(TAG, JSON.stringify({ event: 'startPPT last ppt' }));
    return;
  }

  await prepareNextPPT(ppt);
};

const prepareNextPPT = async (currentPPT: PPT): Promise<void> => {
  const { PPTList } = state;
  const { videoType, roundId: pptRoundId, index } = currentPPT;
  if (videoType === VideoType.RollCall) {
    const submitRightPPT = PPTList.find(
      (p): boolean =>
        p.roundId === pptRoundId && p.videoType === VideoType.SubmitRight
    );
    if (submitRightPPT) {
      await preparePPT(submitRightPPT);
    }
    const submitWrongPPT = PPTList.find(
      (p): boolean =>
        p.roundId === pptRoundId && p.videoType === VideoType.SubmitWrong
    );
    if (submitWrongPPT) {
      await preparePPT(submitWrongPPT);
    }
  } else if (videoType === VideoType.SubmitRight) {
    if (PPTList.length > index + 2) {
      const nextPPT = PPTList[index + 2];
      await preparePPT(nextPPT);
    }
  } else if (PPTList.length > index + 1) {
    const nextPPT = PPTList[index + 1];
    await preparePPT(nextPPT);
  }
};

export const nextPPT = async (): Promise<void> => {
  Log.i(TAG, JSON.stringify({ event: 'nextPPT' }));

  const { PPTList, currentPPT } = state;
  const { index, roundId, videoType, instantAnswer } = currentPPT;
  if (videoType === VideoType.RollCall && instantAnswer) {
    const { answerState } = instantAnswer;
    let nextVideoType = VideoType.SubmitRight;
    if (!answerState || answerState === AnswerState.Wrong) {
      nextVideoType = VideoType.SubmitWrong;
    }

    const next = PPTList.find(
      (ppt): boolean =>
        ppt.roundId === roundId && ppt.videoType === nextVideoType
    );
    if (next) {
      await startPPT({ ppt: next });
      return;
    }
  } else if (videoType === VideoType.SubmitRight) {
    const ppt = PPTList[index + 2];
    await startPPT({ ppt });
    return;
  }

  const ppt = PPTList[index + 1];
  await startPPT({ ppt });
};

export const clearPPTPlayer = (): void => {
  pptPlayer = {};
};

export const setScroll = (scroll: any): void => {
  pptContainer = scroll;
};

export const pptScrollTo = (index: number): Promise<void> =>
  new Promise((resolve): void => {
    Log.i(TAG, JSON.stringify({ event: 'pptScrollTo', index }));
    if (pptContainer) {
      pptContainer.scrollTo({
        x: width * index,
        y: 0,
        animated: false
      });
    }
    setTimeout((): void => {
      resolve();
    }, 300);
  });

export const syncPPT = async (
  round: CurrentRoundInfo,
  needCheckBuffer = true
): Promise<void> => {
  const {
    explainVideoProgress,
    doProblemProgress,
    roundProgress,
    roundId,
    roundStatus
  } = round;
  const { currentPPT } = state;
  const currentTime = getCurrentTime();

  if (roundId !== currentPPT.roundId) {
    Log.e(
      TAG,
      JSON.stringify({
        event: 'syncPPT roundId different',
        roundId,
        currentPPT
      })
    );
    return;
  }

  if (currentPPT.type === PPTType.Problem) {
    if (roundStatus === ProblemRoundStatus.Analysis) {
      startClassRoomWithProblemRound(round);
      shellExec.makeSureLongLinkConnected();
    }
    return;
  }

  let progress = 0;
  let seekBuffer = 0;
  if (currentPPT.videoType === VideoType.Main) {
    progress = roundProgress;
    seekBuffer = Math.abs(currentTime - roundProgress);
  } else if (currentPPT.videoType === VideoType.Analysis) {
    progress = explainVideoProgress;
    seekBuffer = Math.abs(currentTime - explainVideoProgress);
  } else if (currentPPT.videoType === VideoType.RollCall) {
    progress = doProblemProgress;
    seekBuffer = Math.abs(currentTime - doProblemProgress);
  } else if (
    currentPPT.videoType === VideoType.SubmitRight ||
    currentPPT.videoType === VideoType.SubmitWrong
  ) {
    progress = explainVideoProgress;
    seekBuffer = Math.abs(currentTime - explainVideoProgress);
  }

  if (needCheckBuffer && seekBuffer < SYNC_ROUND_INFO_BUFFER) {
    return;
  }

  const { index } = currentPPT;
  const currentPPTPlayer = pptPlayer[`index-${index}`];
  if (!currentPPTPlayer) {
    return;
  }

  Log.i(
    TAG,
    JSON.stringify({
      event: 'syncPPT',
      currentPPT,
      currentTime,
      explainVideoProgress,
      roundProgress,
      needCheckBuffer,
      round
    })
  );

  clearUserAnswerInfo();
  await currentPPTPlayer.seek(progress);
};

export const updateClassRoomRound = (time: number): void => {
  Log.i(
    TAG,
    JSON.stringify({
      event: 'updateClassRoomRound',
      time
    })
  );
  const { rounds, type } = state;
  if (type !== ClassRoomType.Vod) return;

  const round = rounds.find((r): boolean => {
    const { aboveTotalTime, duration } = r;
    return aboveTotalTime <= time && time <= aboveTotalTime + duration;
  });
  if (!round) return;

  let currentRound: CurrentRoundInfo;
  const { aboveTotalTime, roundInfoProblem, roundInfoRollCall } = round;
  const roundProgress = time - aboveTotalTime;
  Log.i(
    TAG,
    JSON.stringify({
      event: 'updateClassRoomRound',
      time,
      aboveTotalTime,
      roundProgress
    })
  );
  if (round.roundType === RoundType.Problem) {
    const { answerDuration } = roundInfoProblem;
    let doProblemProgress = roundProgress;
    if (roundProgress > answerDuration) {
      doProblemProgress = answerDuration;
    } else if (roundProgress === answerDuration) {
      doProblemProgress = answerDuration - 1000;
    }
    currentRound = {
      ...round,
      doProblemProgress,
      explainVideoProgress:
        roundProgress > answerDuration ? roundProgress - answerDuration : 0,
      roundStatus:
        roundProgress > answerDuration
          ? ProblemRoundStatus.Analysis
          : ProblemRoundStatus.DoProblemAfterDiscuss,
      roundProgress,
      submitProblemUsers: [],
      problemAnswer: {
        problemId: 0,
        withSmall: false,
        answer: [],
        smallAnswers: []
      },
      problemResult: {
        problemId: 0,
        withSmall: false,
        isCorrect: false,
        results: [],
        addGoldCoinNum: 0
      }
    };
    startClassRoomWithProblemRound(currentRound);
  } else if (round.roundType === RoundType.RollCall) {
    const { rollCallVideoDuration, instantAnswers } = roundInfoRollCall;

    if (instantAnswers.length > 0) {
      const instantAnswer = instantAnswers[0];
      const { duration, startTime } = instantAnswer;
      if (roundProgress > duration + startTime) {
        const currentVideoRoundIndex = rounds.findIndex(
          (r): boolean => r.serialId === round.serialId
        );
        const prepareRoundIndex = currentVideoRoundIndex + 1;
        if (rounds.length > prepareRoundIndex) {
          const nextRoundInfo = rounds[prepareRoundIndex];
          if (nextRoundInfo) {
            initRoundInfo(nextRoundInfo);
          }
          return;
        }
      }
    }

    let doProblemProgress = roundProgress;
    if (roundProgress > rollCallVideoDuration) {
      doProblemProgress = rollCallVideoDuration;
    } else if (roundProgress === rollCallVideoDuration) {
      doProblemProgress = rollCallVideoDuration - 1000;
    }
    currentRound = {
      ...round,
      doProblemProgress,
      explainVideoProgress:
        roundProgress > rollCallVideoDuration
          ? roundProgress - rollCallVideoDuration
          : 0,
      roundStatus:
        roundProgress > rollCallVideoDuration
          ? ProblemRoundStatus.Analysis
          : ProblemRoundStatus.DoProblemAfterDiscuss,
      roundProgress,
      submitProblemUsers: [],
      problemAnswer: {
        problemId: 0,
        withSmall: false,
        answer: [],
        smallAnswers: []
      },
      problemResult: {
        problemId: 0,
        withSmall: false,
        isCorrect: false,
        results: [],
        addGoldCoinNum: 0
      }
    };
    startClassRoomWithRollCallRound(currentRound);
  } else {
    currentRound = {
      ...round,
      doProblemProgress: 0,
      explainVideoProgress: 0,
      roundStatus: ProblemRoundStatus.Unknown,
      roundProgress,
      submitProblemUsers: [],
      problemAnswer: {
        problemId: 0,
        withSmall: false,
        answer: [],
        smallAnswers: []
      },
      problemResult: {
        problemId: 0,
        withSmall: false,
        isCorrect: false,
        results: [],
        addGoldCoinNum: 0
      }
    };
    startClassRoomWithVideoRound(currentRound);
  }

  setClassRoomCurrentTime(time);
};

export const clearBarStatusChangeTimer = (): void => {
  if (barStatusChangeTimer) {
    clearTimeout(barStatusChangeTimer);
    barStatusChangeTimer = null;
  }
};

export const changeBarStatus = (show: boolean): void => {
  setShowBar(show);
  clearBarStatusChangeTimer();
  if (show) {
    barStatusChangeTimer = setTimeout((): void => {
      setShowBar(false);
    }, 5 * 1000);
  }
};

export const titleChangeBarStatus = (): void => {
  const { showBar } = state;
  changeBarStatus(!showBar);
};

export const seekWithTime = (time: number): void => {
  Log.i(
    TAG,
    JSON.stringify({
      event: 'seekWithTime',
      time
    })
  );
  if (time < 0) {
    Log.e(
      TAG,
      JSON.stringify({
        event: 'seekWithTime unValid time',
        time
      })
    );
    return;
  }
  if (nextSeekNumber === time) {
    Log.e(
      TAG,
      JSON.stringify({
        event: 'seekWithTime existTime',
        time
      })
    );
    return;
  }
  if (haveSeek || nextSeekNumber !== -1) {
    Log.e(
      TAG,
      JSON.stringify({
        event: 'seekWithTime haveSeek',
        time,
        nextSeekNumber
      })
    );
    nextSeekNumber = time;
    return;
  }
  updateClassRoomRound(time);
};

export const nextSeekTime = (): void => {
  Log.i(
    TAG,
    JSON.stringify({
      event: 'nextSeekTime',
      nextSeekNumber
    })
  );
  if (nextSeekNumber === -1) {
    setClassRoomChangeTime(-1);
    return;
  }
  if (nextSeekNumber !== -1) {
    updateClassRoomRound(nextSeekNumber);
    nextSeekNumber = -1;
  }
};

export const clearSeekTimeList = (): void => {
  haveSeek = false;
  nextSeekNumber = -1;
};

export const lessonSign = async (): Promise<void> => {
  setShowContainerLoading(true);
  const res = await ClassApi.lessonSign({ lessonId: localLessonId });
  const { code, data } = res;
  setShowContainerLoading(false);
  if (code === 0) {
    const { goldCoinChangeNum } = data;
    let { users } = state;
    emitSignInSuccess({
      addGoldCoinNum: goldCoinChangeNum
    });
    users = users.map(
      (user): LiveUser => {
        const { userId } = getLocalUserInfo();
        if (Number(user.userId) === Number(userId)) {
          return {
            ...user,
            goldCoinNum: user.goldCoinNum + goldCoinChangeNum
          };
        }
        return user;
      }
    );
    setCoinProgress(9);
    setUsers(users);
    setShowSignIn(false);
    setSignInTime(0);
    setShowSignSuccess(true);
    return;
  }
  lessonSign();
};

export const addCoinNumber = (): void => {
  let { users } = state;
  const { type } = state;
  users = users.map(
    (user): LiveUser => {
      const { userId } = getLocalUserInfo();
      updateUserGoldCoinNum(localLessonId, type)(user.goldCoinNum + 1);
      emitShowFaceSuccess({ addGoldCoinNum: 1 });
      if (Number(user.userId) === Number(userId)) {
        return {
          ...user,
          goldCoinNum: user.goldCoinNum + 1
        };
      }
      return user;
    }
  );
  setUsers(users);
};

export const showFace = (): void => {
  const { type } = state;
  if (type === ClassRoomType.Vod) return;
  ClassApi.showFace({ lessonId: localLessonId });
};

export const getProblemIsAllWrong = (): boolean => {
  const { problemResult } = state;
  const { withSmall, isCorrect, results } = problemResult;

  let wrongNumber = 0;

  if (!withSmall) {
    wrongNumber = isCorrect ? 0 : 1;
  } else {
    wrongNumber = results.reduce((total, current): number => {
      const val = current.isCorrect ? 0 : 1;
      return total + val;
    }, 0);
  }

  const allWrong =
    (withSmall && wrongNumber === results.length) ||
    (!withSmall && wrongNumber === 1);

  return allWrong;
};

export const checkUserAnswerAfterSoundEnd = (): void => {
  const { users } = state;
  clearAllFinishProblemTimer();
  haveTeacherAudio = false;
  checkUserAnswer(users);
};

export const setSoundUrlInfo = (urlList: TeacherAudio[]): void => {
  let { soundUrl, soundUrlList } = state;

  const validUrlList = urlList.filter(
    (url): boolean => url.duration > 0 && !!url.url
  );
  if (validUrlList.length === 0 && soundUrlList.length === 0 && !soundUrl) {
    Log.e(
      TAG,
      JSON.stringify({ event: 'setSoundUrlInfo no valid url list', urlList })
    );
    checkUserAnswerAfterSoundEnd();
    return;
  }

  if (soundUrl) {
    soundUrlList = [...soundUrlList, ...validUrlList];
  } else {
    const [url, ...list] = validUrlList;
    soundUrl = url;
    soundUrlList = list;
  }

  clearAllFinishProblemTimer();
  if (soundUrl.duration > (problemRemainTime - 1) * 1000) {
    soundUrl = undefined;
    soundUrlList = [];
    checkUserAnswerAfterSoundEnd();
  }

  setSoundUrl(soundUrl);
  setSoundUrlList(soundUrlList);
};

export const onEndSoundPlayer = (): void => {
  let { soundUrl, soundUrlList } = state;
  if (soundUrlList.length > 0) {
    const [url, ...list] = soundUrlList;
    soundUrl = url;
    soundUrlList = list;
  } else {
    soundUrl = undefined;
  }

  clearAllFinishProblemTimer();
  if (!soundUrl) {
    checkUserAnswerAfterSoundEnd();
  }

  if (soundUrl && soundUrl.duration > (problemRemainTime - 1) * 1000) {
    soundUrl = undefined;
    soundUrlList = [];
    checkUserAnswerAfterSoundEnd();
  }

  setSoundUrl(soundUrl);
  setSoundUrlList(soundUrlList);
};

let isGetRollCallStudentInfo = false;

export const getRollCallStudentInfo = async (
  roundId: number
): Promise<void> => {
  if (!roundId) return;

  isGetRollCallStudentInfo = true;
  const res = await ClassApi.getPlatformStudent({
    roundId,
    lessonId: localLessonId
  });
  const { code, data } = res;
  if (code !== 0) {
    isGetRollCallStudentInfo = false;
    return;
  }

  const { studentId, studentName, teacherAudios } = data;

  const rollCallAudio = teacherAudios.find(
    (audio): boolean => Number(audio.sceneId) === AUDIO_SCENE_ID.RollCall
  );
  const submitRightAudio = teacherAudios.find(
    (audio): boolean => Number(audio.sceneId) === AUDIO_SCENE_ID.SubmitRight
  );
  const submitWrongAudio = teacherAudios.find(
    (audio): boolean => Number(audio.sceneId) === AUDIO_SCENE_ID.SubmitWrong
  );

  let { PPTList } = state;
  PPTList = PPTList.map(
    (ppt): PPT => {
      if (ppt.roundId === roundId) {
        if (ppt.videoType === VideoType.RollCall) {
          return {
            ...ppt,
            rollCallUserInfo: {
              userId: Number(studentId),
              userName: studentName
            },
            audio: rollCallAudio
          };
        }
        if (ppt.videoType === VideoType.SubmitRight) {
          return {
            ...ppt,
            rollCallUserInfo: {
              userId: Number(studentId),
              userName: studentName
            },
            audio: submitRightAudio
          };
        }
        if (ppt.videoType === VideoType.SubmitWrong) {
          return {
            ...ppt,
            rollCallUserInfo: {
              userId: Number(studentId),
              userName: studentName
            },
            audio: submitWrongAudio
          };
        }
      }
      return ppt;
    }
  );

  setPPTList(PPTList);
  setTimeout((): void => {
    isGetRollCallStudentInfo = false;
  });
};

export const prepareRollCallInfo = (): void => {
  const { PPTList, type } = state;
  if (type === ClassRoomType.Vod) return;
  if (isGetRollCallStudentInfo) return;

  const round = getNextRound();
  if (!round) return;

  const nextPPTInfo = PPTList.find(
    (ppt): boolean =>
      ppt.roundId === round.roundId && ppt.videoType === VideoType.RollCall
  );
  if (!nextPPTInfo) return;

  if (
    nextPPTInfo.rollCallUserInfo &&
    nextPPTInfo.rollCallUserInfo.userId === -1
  ) {
    getRollCallStudentInfo(round.roundId);
  }
};

export const updateCurrentPPTWithAnswerState = (
  answerState: AnswerState
): void => {
  const { currentPPT } = state;
  const { instantAnswer } = currentPPT;

  if (instantAnswer) {
    setCurrentPPT({
      ...currentPPT,
      instantAnswer: {
        ...instantAnswer,
        answerState
      }
    });
  }
};

export const recordUpdateAudioAvailable = (available: boolean): void => {
  const needOpenMicrophone = getNeedOpenMicrophone();
  if (needOpenMicrophone) {
    setAudioAvailable(available, 'updateAudioAvailable');
  }
};
